From 6183848902d2b4ce79a7c3f6dd073d5a2ecc3548 Mon Sep 17 00:00:00 2001 From: Pedro Carrasco Date: Fri, 28 Sep 2018 17:30:19 +0100 Subject: [PATCH] LayoutItem Improvements (#13) * Improved Logic behind ConstrictoCore * Bumped Spec * Code Review * Updated Project Structure --- Constrictor.podspec | 2 +- .../Constrictor.xcodeproj/project.pbxproj | 50 ++- .../Constrictable+ConstrictorCore.swift | 45 +-- .../Factory/LayoutItemFactory.swift | 262 ++++++++++++++++ .../ItemLayoutAttributesDecoder.swift | 293 ------------------ .../Constrictor/Structs/LayoutItem.swift | 56 ++++ .../Constrictable+Constrictor.swift | 0 .../Constrictable+ConstrictorCenter.swift | 0 .../Constrictable+ConstrictorEdges.swift | 0 ...sts.swift => LayoutItemFactoryTests.swift} | 81 ++--- Example/Example/ViewController.swift | 8 +- Example/Podfile.lock | 6 +- 12 files changed, 428 insertions(+), 375 deletions(-) rename Constrictor/Constrictor/{Extensions => Core}/Constrictable+ConstrictorCore.swift (69%) create mode 100644 Constrictor/Constrictor/Factory/LayoutItemFactory.swift delete mode 100644 Constrictor/Constrictor/Handlers/ItemLayoutAttributesDecoder.swift create mode 100644 Constrictor/Constrictor/Structs/LayoutItem.swift rename Constrictor/Constrictor/{Extensions => Sugar}/Constrictable+Constrictor.swift (100%) rename Constrictor/Constrictor/{Extensions => Sugar}/Constrictable+ConstrictorCenter.swift (100%) rename Constrictor/Constrictor/{Extensions => Sugar}/Constrictable+ConstrictorEdges.swift (100%) rename Constrictor/ConstrictorTests/Tests/Enumerations/{ItemLayoutAttributesDecoderTests.swift => LayoutItemFactoryTests.swift} (85%) diff --git a/Constrictor.podspec b/Constrictor.podspec index 10240ca..c32b9fc 100644 --- a/Constrictor.podspec +++ b/Constrictor.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.swift_version = "4.2" s.name = "Constrictor" - s.version = "3.0.0" + s.version = "3.0.1" s.summary = "🐍 AutoLayout's µFramework" s.description = "(Boe) Constrictor's AutoLayout µFramework with the goal of simplying your constraints by reducing the amount of code you have to write." diff --git a/Constrictor/Constrictor.xcodeproj/project.pbxproj b/Constrictor/Constrictor.xcodeproj/project.pbxproj index 30a81ae..f334803 100644 --- a/Constrictor/Constrictor.xcodeproj/project.pbxproj +++ b/Constrictor/Constrictor.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ 20D1F2BC20B35D4C00B327B7 /* Constrictable+ConstrictorCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D1F2BB20B35D4C00B327B7 /* Constrictable+ConstrictorCenter.swift */; }; 20D1F2BE20B35FBC00B327B7 /* Constrictable+ConstrictorEdges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D1F2BD20B35FBC00B327B7 /* Constrictable+ConstrictorEdges.swift */; }; 20D364DC20B6291000EF02B2 /* UIView+ConstrictorCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D364DB20B6291000EF02B2 /* UIView+ConstrictorCenterTests.swift */; }; - 534B3A8D2139C680009D9F74 /* ItemLayoutAttributesDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534B3A8C2139C680009D9F74 /* ItemLayoutAttributesDecoder.swift */; }; + 531188D8215E364D00148AF3 /* LayoutItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531188D7215E364D00148AF3 /* LayoutItemFactory.swift */; }; 537F0042213B424300BC0354 /* ConstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537F0041213B424300BC0354 /* ConstantTests.swift */; }; 539B841820B6C7DF00C85514 /* UIView+ConstrictorEdgesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539B841720B6C7DF00C85514 /* UIView+ConstrictorEdgesTests.swift */; }; 53CB882320B4618B002731A6 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CB882220B4618B002731A6 /* Constant.swift */; }; @@ -30,7 +30,8 @@ 53D819A420B5A4E700E62D1E /* ConstraintIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D819A320B5A4E700E62D1E /* ConstraintIndex.swift */; }; 53D819A620B5A58800E62D1E /* UIView+Finder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D819A520B5A58800E62D1E /* UIView+Finder.swift */; }; 53D819AB20B5B1F300E62D1E /* ConstraintTestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D819AA20B5B1F300E62D1E /* ConstraintTestable.swift */; }; - 53ED274920BC012C00038282 /* ItemLayoutAttributesDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED274820BC012C00038282 /* ItemLayoutAttributesDecoderTests.swift */; }; + 53E6F33C215D4C6200E0337E /* LayoutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E6F33B215D4C6200E0337E /* LayoutItem.swift */; }; + 53ED274920BC012C00038282 /* LayoutItemFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED274820BC012C00038282 /* LayoutItemFactoryTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,7 +56,7 @@ 20D1F2BB20B35D4C00B327B7 /* Constrictable+ConstrictorCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constrictable+ConstrictorCenter.swift"; sourceTree = ""; }; 20D1F2BD20B35FBC00B327B7 /* Constrictable+ConstrictorEdges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constrictable+ConstrictorEdges.swift"; sourceTree = ""; }; 20D364DB20B6291000EF02B2 /* UIView+ConstrictorCenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ConstrictorCenterTests.swift"; sourceTree = ""; }; - 534B3A8C2139C680009D9F74 /* ItemLayoutAttributesDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLayoutAttributesDecoder.swift; sourceTree = ""; }; + 531188D7215E364D00148AF3 /* LayoutItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutItemFactory.swift; sourceTree = ""; }; 537F0041213B424300BC0354 /* ConstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantTests.swift; sourceTree = ""; }; 539B841720B6C7DF00C85514 /* UIView+ConstrictorEdgesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ConstrictorEdgesTests.swift"; sourceTree = ""; }; 53CB882220B4618B002731A6 /* Constant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; @@ -70,7 +71,8 @@ 53D819A320B5A4E700E62D1E /* ConstraintIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstraintIndex.swift; sourceTree = ""; }; 53D819A520B5A58800E62D1E /* UIView+Finder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Finder.swift"; sourceTree = ""; }; 53D819AA20B5B1F300E62D1E /* ConstraintTestable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstraintTestable.swift; sourceTree = ""; }; - 53ED274820BC012C00038282 /* ItemLayoutAttributesDecoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLayoutAttributesDecoderTests.swift; sourceTree = ""; }; + 53E6F33B215D4C6200E0337E /* LayoutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutItem.swift; sourceTree = ""; }; + 53ED274820BC012C00038282 /* LayoutItemFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutItemFactoryTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -111,10 +113,6 @@ 20D1F2B820B3501400B327B7 /* Extensions */ = { isa = PBXGroup; children = ( - 53CDE3CC20B3204D007E4AE0 /* Constrictable+Constrictor.swift */, - 20BC12FC20B98A240034207F /* Constrictable+ConstrictorCore.swift */, - 20D1F2BB20B35D4C00B327B7 /* Constrictable+ConstrictorCenter.swift */, - 20D1F2BD20B35FBC00B327B7 /* Constrictable+ConstrictorEdges.swift */, 205B527D20BADEA50016C8B8 /* UIView+Constrictable.swift */, 205B527F20BADEB10016C8B8 /* UIViewController+Constrictable.swift */, 205B528820BAE5E20016C8B8 /* UILayoutGuide+Constrictable.swift */, @@ -123,18 +121,37 @@ path = Extensions; sourceTree = ""; }; - 534B3A8B2139C61E009D9F74 /* Handlers */ = { + 531188D9215E861400148AF3 /* Core */ = { + isa = PBXGroup; + children = ( + 20BC12FC20B98A240034207F /* Constrictable+ConstrictorCore.swift */, + ); + path = Core; + sourceTree = ""; + }; + 531188DA215E862100148AF3 /* Sugar */ = { + isa = PBXGroup; + children = ( + 53CDE3CC20B3204D007E4AE0 /* Constrictable+Constrictor.swift */, + 20D1F2BB20B35D4C00B327B7 /* Constrictable+ConstrictorCenter.swift */, + 20D1F2BD20B35FBC00B327B7 /* Constrictable+ConstrictorEdges.swift */, + ); + path = Sugar; + sourceTree = ""; + }; + 534B3A8B2139C61E009D9F74 /* Factory */ = { isa = PBXGroup; children = ( - 534B3A8C2139C680009D9F74 /* ItemLayoutAttributesDecoder.swift */, + 531188D7215E364D00148AF3 /* LayoutItemFactory.swift */, ); - path = Handlers; + path = Factory; sourceTree = ""; }; 53CB882120B46176002731A6 /* Structs */ = { isa = PBXGroup; children = ( 53CB882220B4618B002731A6 /* Constant.swift */, + 53E6F33B215D4C6200E0337E /* LayoutItem.swift */, ); path = Structs; sourceTree = ""; @@ -160,7 +177,9 @@ 53CDE3C020B3201E007E4AE0 /* Constrictor */ = { isa = PBXGroup; children = ( - 534B3A8B2139C61E009D9F74 /* Handlers */, + 531188D9215E861400148AF3 /* Core */, + 531188DA215E862100148AF3 /* Sugar */, + 534B3A8B2139C61E009D9F74 /* Factory */, 205B528520BADF290016C8B8 /* Protocols */, 205B528420BADF220016C8B8 /* Enumerations */, 53CB882120B46176002731A6 /* Structs */, @@ -240,7 +259,7 @@ 53ED274720BC011800038282 /* Enumerations */ = { isa = PBXGroup; children = ( - 53ED274820BC012C00038282 /* ItemLayoutAttributesDecoderTests.swift */, + 53ED274820BC012C00038282 /* LayoutItemFactoryTests.swift */, ); path = Enumerations; sourceTree = ""; @@ -357,7 +376,8 @@ files = ( 20D1F2BC20B35D4C00B327B7 /* Constrictable+ConstrictorCenter.swift in Sources */, 205B527E20BADEA50016C8B8 /* UIView+Constrictable.swift in Sources */, - 534B3A8D2139C680009D9F74 /* ItemLayoutAttributesDecoder.swift in Sources */, + 53E6F33C215D4C6200E0337E /* LayoutItem.swift in Sources */, + 531188D8215E364D00148AF3 /* LayoutItemFactory.swift in Sources */, 205B528920BAE5E20016C8B8 /* UILayoutGuide+Constrictable.swift in Sources */, 202473B620BA14E9005693AC /* ConstrictorAttribute.swift in Sources */, 53CDE3CD20B3204D007E4AE0 /* Constrictable+Constrictor.swift in Sources */, @@ -380,7 +400,7 @@ 53D8199520B5818800E62D1E /* UIView+ConstrictorTests.swift in Sources */, 20D364DC20B6291000EF02B2 /* UIView+ConstrictorCenterTests.swift in Sources */, 53D819A420B5A4E700E62D1E /* ConstraintIndex.swift in Sources */, - 53ED274920BC012C00038282 /* ItemLayoutAttributesDecoderTests.swift in Sources */, + 53ED274920BC012C00038282 /* LayoutItemFactoryTests.swift in Sources */, 53D819AB20B5B1F300E62D1E /* ConstraintTestable.swift in Sources */, 539B841820B6C7DF00C85514 /* UIView+ConstrictorEdgesTests.swift in Sources */, 53D819A220B5A2CE00E62D1E /* NSLayoutAttribute+Finder.swift in Sources */, diff --git a/Constrictor/Constrictor/Extensions/Constrictable+ConstrictorCore.swift b/Constrictor/Constrictor/Core/Constrictable+ConstrictorCore.swift similarity index 69% rename from Constrictor/Constrictor/Extensions/Constrictable+ConstrictorCore.swift rename to Constrictor/Constrictor/Core/Constrictable+ConstrictorCore.swift index 622b47a..1f0d4da 100644 --- a/Constrictor/Constrictor/Extensions/Constrictable+ConstrictorCore.swift +++ b/Constrictor/Constrictor/Core/Constrictable+ConstrictorCore.swift @@ -30,23 +30,22 @@ extension Constrictable { func constrict(_ selfAttribute: ConstrictorAttribute, relation: NSLayoutConstraint.Relation = .equal, to item: Constrictable, attribute: ConstrictorAttribute, constant: Constant, multiplier: CGFloat = 1.0, priority: UILayoutPriority = .required) { - - let firstLayoutAttributes = ItemLayoutAttributesDecoder.itemLayoutAttribute(for: self, with: selfAttribute, and: constant) - let secondLayoutAttributes = ItemLayoutAttributesDecoder.itemLayoutAttribute(for: item, with: attribute, and: constant) - if let constrictableAsView = self as? UIView { + prepareForAutoLayout() - constrictableAsView.translatesAutoresizingMaskIntoConstraints = false - } + let items = LayoutItemFactory.makeLayoutItems(firstElement: self, + secondElement: item, + firstAttribute: selfAttribute, + secondAttribute: attribute, + constant: constant) - NSLayoutConstraint(item: self, - attribute: firstLayoutAttributes.layoutAttribute, + attribute: items.head.attribute, relatedBy: relation, - toItem: secondLayoutAttributes.item, - attribute: secondLayoutAttributes.layoutAttribute, + toItem: items.tail.element, + attribute: items.tail.attribute, multiplier: multiplier, - constant: firstLayoutAttributes.constant).isActive = true + constant: items.head.constant).isActive = true } /** @@ -66,20 +65,28 @@ extension Constrictable { func constrict(_ selfAttribute: ConstrictorAttribute, relation: NSLayoutConstraint.Relation = .equal, constant: Constant, multiplier: CGFloat = 1.0, priority: UILayoutPriority = .required) { - - let layoutAttributes = ItemLayoutAttributesDecoder.itemLayoutAttribute(for: self, with: selfAttribute, and: constant) - - if let constrictableAsView = self as? UIView { - constrictableAsView.translatesAutoresizingMaskIntoConstraints = false - } + prepareForAutoLayout() + + let item = LayoutItemFactory.makeLayoutItem(element: self, + attribute: selfAttribute, + constant: constant) NSLayoutConstraint(item: self, - attribute: layoutAttributes.layoutAttribute, + attribute: item.attribute, relatedBy: relation, toItem: nil, attribute: .notAnAttribute, multiplier: multiplier, - constant: layoutAttributes.constant).isActive = true + constant: item.constant).isActive = true + } + + + func prepareForAutoLayout() { + + if let constrictableAsView = self as? UIView { + + constrictableAsView.translatesAutoresizingMaskIntoConstraints = false + } } } diff --git a/Constrictor/Constrictor/Factory/LayoutItemFactory.swift b/Constrictor/Constrictor/Factory/LayoutItemFactory.swift new file mode 100644 index 0000000..617bda6 --- /dev/null +++ b/Constrictor/Constrictor/Factory/LayoutItemFactory.swift @@ -0,0 +1,262 @@ +// +// LayoutItemFactory.swift +// Constrictor +// +// Created by Pedro Carrasco on 28/09/2018. +// Copyright © 2018 Pedro Carrasco. All rights reserved. +// + +import Foundation + +// MARK: - LayoutItemFactory +struct LayoutItemFactory { + + /** + Converts a set of attributes, elements and constant into two LayoutItems + + - parameters: + - firstElement: Optional Constrictable's to extract NSLayoutConstraint.Attribute from. + - secondElement: Optional Constrictable's to extract NSLayoutConstraint.Attribute from. + - firstAttribute: ConstrictorAttributor from the first element. + - secondAttribute: ConstrictorAttributor from the second element. + - constant: Constant to be applied to the first element. + + - returns: + Tuple containing an HeadLayoutImte & TailLayoutItem (starting anchor and ending anchor). + */ + static func makeLayoutItems(firstElement: Constrictable?, secondElement: Constrictable?, + firstAttribute: ConstrictorAttribute, secondAttribute: ConstrictorAttribute, + constant: Constant) -> (head: HeadLayoutItem, tail: TailLayoutItem) { + + let firstItem = layoutItem(for: firstElement, attribute: firstAttribute, constant: constant) + let secondItem = layoutItem(for: secondElement, attribute: secondAttribute, constant: constant) + + return (firstItem.asHead, secondItem.asTail) + } + + /** + Converts an attribute, element and constant into a LayoutItem + + - parameters: + - element: Optional Constrictable's to extract NSLayoutConstraint.Attribute from. + - attribute: ConstrictorAttributor from the element. + - constant: Constant to be applied. + + - returns: + HeadLayoutItem + */ + + static func makeLayoutItem(element: Constrictable?, + attribute: ConstrictorAttribute, + constant: Constant) -> HeadLayoutItem { + + return layoutItem(for: element, attribute: attribute, constant: constant).asHead + } +} + + +// MARK: - Decision Maker +private extension LayoutItemFactory { + + + static func layoutItem(for element: Constrictable?, + attribute: ConstrictorAttribute, + constant: Constant) -> LayoutItem { + + var item = LayoutItem() + + if let view = element as? UIView { + item = layoutItem(view: view, with: attribute, and: constant) + } else if let viewController = element as? UIViewController { + item = layoutItem(viewController: viewController, with: attribute, and: constant) + } else if let layoutGuide = element as? UILayoutGuide { + item = layoutItem(layoutGuide: layoutGuide, with: attribute, and: constant) + } + + return item + } + + static func layoutItem(layoutGuide: UILayoutGuide, + with attribute: ConstrictorAttribute, + and constantStruct: Constant) -> LayoutItem { + + return buildLayoutItem(for: attribute, with: constantStruct, safeArea: layoutGuide) + } + + static func layoutItem(view: UIView, + with attribute: ConstrictorAttribute, + and constantStruct: Constant) -> LayoutItem { + + let safeArea: Any + + if #available(iOS 11.0, *), ConstrictorAttribute.guidedAttributes.contains(attribute) { + safeArea = view.safeAreaLayoutGuide + } else { + safeArea = view + } + + return buildLayoutItem(for: attribute, with: constantStruct, safeArea: safeArea) + } + + static func layoutItem(viewController: UIViewController, + with constrictorAttribute: ConstrictorAttribute, + and constantStruct: Constant) -> LayoutItem { + + let safeArea: Any + let atLeastiOS11: Bool + + if #available(iOS 11.0, *), ConstrictorAttribute.guidedAttributes.contains(constrictorAttribute) { + safeArea = viewController.view.safeAreaLayoutGuide + atLeastiOS11 = true + } else { + safeArea = viewController.view + atLeastiOS11 = false + } + + return buildLayoutItem(for: constrictorAttribute, + with: constantStruct, + viewController: viewController, + atLeastiOS11: atLeastiOS11, + safeArea: safeArea) + } +} + +// MARK: - Builder +private extension LayoutItemFactory { + + static func buildLayoutItem(for constrictorAttribute: ConstrictorAttribute, + with constantStruct: Constant, + viewController: UIViewController? = nil, + atLeastiOS11: Bool? = nil, + safeArea: Any) -> LayoutItem { + + let attribute: NSLayoutConstraint.Attribute + let constant: CGFloat + var element = safeArea + + switch constrictorAttribute { + case .top: + attribute = .top + constant = constantStruct.top + + case .topGuide: + attribute = .top + constant = constantStruct.top + + if let atLeastiOS11 = atLeastiOS11, !atLeastiOS11, let vc = viewController { element = vc.topLayoutGuide + } else { element = safeArea } + + case .bottom: + attribute = .bottom + constant = -constantStruct.bottom + + case .bottomGuide: + attribute = .bottom + constant = -constantStruct.bottom + + if let atLeastiOS11 = atLeastiOS11, !atLeastiOS11, let vc = viewController { element = vc.bottomLayoutGuide + } else { element = safeArea } + + case .right, .rightGuide: + attribute = .right + constant = -constantStruct.right + + case .left, .leftGuide: + attribute = .left + constant = constantStruct.left + + case .leading, .leadingGuide: + attribute = .leading + constant = constantStruct.leading + + case .trailing, .trailingGuide: + attribute = .trailing + constant = -constantStruct.trailing + + case .centerX: + attribute = .centerX + constant = constantStruct.x + + case .centerXGuide: + attribute = .centerX + constant = constantStruct.x + + if let atLeastiOS11 = atLeastiOS11, !atLeastiOS11, let vc = viewController { + element = safeLayoutGuide(for: vc) + } else { element = safeArea } + + case .centerY: + attribute = .centerY + constant = constantStruct.y + + case .centerYGuide: + attribute = .centerY + constant = constantStruct.y + + if let atLeastiOS11 = atLeastiOS11, !atLeastiOS11, let vc = viewController { + element = safeLayoutGuide(for: vc) + } else { element = safeArea } + + case .width: + attribute = .width + constant = constantStruct.width + + case .height: + attribute = .height + constant = constantStruct.height + + case .none: + attribute = .notAnAttribute + constant = 0.0 + } + + return LayoutItem(element: element, attribute: attribute, constant: constant) + } +} + +// MARK: - Utils +private extension LayoutItemFactory { + + /** + Get an UILayoutGuide pinned to the viewController's safe edges. + + - parameters: + - viewController: UIViewController to get an UILayoutGuide pinned its edges + + - returns: + Safe UILayoutGuide. + */ + + static func safeLayoutGuide(for viewController: UIViewController) -> UILayoutGuide { + + let layoutGuide = UILayoutGuide() + viewController.view.addLayoutGuide(layoutGuide) + + NSLayoutConstraint.activate( + [ + NSLayoutConstraint( + item: layoutGuide, attribute: .top, relatedBy: .equal, + toItem: viewController.topLayoutGuide, attribute: .bottom, + multiplier: 1, constant: 0 + ), + NSLayoutConstraint( + item: layoutGuide, attribute: .bottom, relatedBy: .equal, + toItem: viewController.bottomLayoutGuide, attribute: .top, + multiplier: 1, constant: 0 + ), + NSLayoutConstraint( + item: layoutGuide, attribute: .leading, relatedBy: .equal, + toItem: viewController.view, attribute: .leading, + multiplier: 1, constant: 0 + ), + NSLayoutConstraint( + item: layoutGuide, attribute: .trailing, relatedBy: .equal, + toItem: viewController.view, attribute: .trailing, + multiplier: 1, constant: 0 + ) + ] + ) + + return layoutGuide + } +} diff --git a/Constrictor/Constrictor/Handlers/ItemLayoutAttributesDecoder.swift b/Constrictor/Constrictor/Handlers/ItemLayoutAttributesDecoder.swift deleted file mode 100644 index 3c752df..0000000 --- a/Constrictor/Constrictor/Handlers/ItemLayoutAttributesDecoder.swift +++ /dev/null @@ -1,293 +0,0 @@ -// -// ItemLayoutAttributesDecoder.swift -// Constrictor -// -// Created by Pedro Carrasco on 31/08/2018. -// Copyright © 2018 Pedro Carrasco. All rights reserved. -// - -import Foundation - -// MARK: - ItemLayoutAttributeDecoder -struct ItemLayoutAttributesDecoder { - - // MARK: Internal Functions - - /** - Converts ConstrictorAttribute to NSLayoutConstraint.Attribute based on an optional Constrictable - - - parameters: - - item: Optional Constrictable's to extract NSLayoutConstraint.Attribute from. - - - returns: - Tuple containing the item to apply a constraint and its attribute. - */ - - static func itemLayoutAttribute(for item: Constrictable?, - with attribute: ConstrictorAttribute, - and constant: Constant) -> (item: Any?, layoutAttribute: NSLayoutConstraint.Attribute, constant: CGFloat) { - - var itemLayoutAttributeTuple: (item: Any?, layoutAttribute: NSLayoutConstraint.Attribute, constant: CGFloat) = (nil, .notAnAttribute, 0.0) - - if let view = item as? UIView { - itemLayoutAttributeTuple = itemLayoutAttribute(view: view, with: attribute, and: constant) - - } else if let viewController = item as? UIViewController { - itemLayoutAttributeTuple = itemLayoutAttribute(viewController: viewController, with: attribute, and: constant) - - } else if let layoutGuide = item as? UILayoutGuide { - itemLayoutAttributeTuple = itemLayoutAttribute(layoutGuide: layoutGuide, with: attribute, and: constant) - } - - return itemLayoutAttributeTuple - } -} - - -// MARK: - Private Functions -private extension ItemLayoutAttributesDecoder { - - /** - Converts ConstrictorAttribute to NSLayoutConstraint.Attribute based on an UILayoutGuite - - - parameters: - - layoutGuide: UILayoutGuide to extract NSLayoutConstraint.Attribute from. - - - returns: - Tuple containing the item to apply a constraint and its attribute. - */ - - static func itemLayoutAttribute(layoutGuide: UILayoutGuide, - with attribute: ConstrictorAttribute, - and constantStruct: Constant) -> (item: Any?, layoutAttribute: NSLayoutConstraint.Attribute, constant: CGFloat) { - - let attributeAndConstantTuple = defaultLayoutAttributeAndConstant(for: attribute, with: constantStruct) - - return (layoutGuide, attributeAndConstantTuple.attribute, attributeAndConstantTuple.constant) - } - - /** - Converts ConstrictorAttribute to NSLayoutConstraint.Attribute based on an UIView - - - parameters: - - view: UIView to extract NSLayoutConstraint.Attribute from. - - - returns: - Tuple containing the item to apply a constraint and its attribute. - */ - - static func itemLayoutAttribute(view: UIView, - with attribute: ConstrictorAttribute, - and constantStruct: Constant) -> (item: Any?, layoutAttribute: NSLayoutConstraint.Attribute, constant: CGFloat) { - - let safeArea: Any - - if #available(iOS 11.0, *), ConstrictorAttribute.guidedAttributes.contains(attribute) { - safeArea = view.safeAreaLayoutGuide - } else { - safeArea = view - } - - let attributeAndConstantTuple = defaultLayoutAttributeAndConstant(for: attribute, with: constantStruct) - - return (safeArea, attributeAndConstantTuple.attribute, attributeAndConstantTuple.constant) - } - - /** - Converts ConstrictorAttribute to NSLayoutConstraint.Attribute based on an UIViewController - - - parameters: - - viewController: UIViewController to extract NSLayoutConstraint.Attribute from. - - - returns: - Tuple containing the item to apply a constraint and its attribute. - */ - - static func itemLayoutAttribute(viewController: UIViewController, - with constrictorAttribute: ConstrictorAttribute, - and constantStruct: Constant) -> (item: Any?, layoutAttribute: NSLayoutConstraint.Attribute, constant: CGFloat) { - - var safeArea: Any - let attribute: NSLayoutConstraint.Attribute - let isIOS11: Bool - let constant: CGFloat - - if #available(iOS 11.0, *), ConstrictorAttribute.guidedAttributes.contains(constrictorAttribute) { - safeArea = viewController.view.safeAreaLayoutGuide - isIOS11 = true - } else { - safeArea = viewController.view - isIOS11 = false - } - - switch constrictorAttribute { - case .top: - attribute = .top - constant = constantStruct.top - - case .topGuide: - safeArea = isIOS11 ? safeArea : viewController.topLayoutGuide - attribute = isIOS11 ? .top : .bottom - constant = constantStruct.top - - case .bottom: - attribute = .bottom - constant = -constantStruct.bottom - - case .bottomGuide: - safeArea = isIOS11 ? safeArea : viewController.bottomLayoutGuide - attribute = isIOS11 ? .bottom : .top - constant = -constantStruct.bottom - - case .right, .rightGuide: - attribute = .right - constant = -constantStruct.right - - case .left, .leftGuide: - attribute = .left - constant = constantStruct.left - - case .leading, .leadingGuide: - attribute = .leading - constant = constantStruct.leading - - case .trailing, .trailingGuide: - attribute = .trailing - constant = -constantStruct.trailing - - case .centerX: - attribute = .centerX - constant = constantStruct.x - - case .centerXGuide: - safeArea = isIOS11 ? safeArea : safeLayoutGuide(for: viewController) - attribute = .centerX - constant = constantStruct.x - - case .centerY: - attribute = .centerY - constant = constantStruct.y - - case .centerYGuide: - safeArea = isIOS11 ? safeArea : safeLayoutGuide(for: viewController) - attribute = .centerY - constant = constantStruct.y - - case .width: - attribute = .width - constant = constantStruct.width - - case .height: - attribute = .height - constant = constantStruct.height - - case .none: - attribute = .notAnAttribute - constant = 0.0 - } - - return (safeArea, attribute, constant) - } -} - -// MARK: - Utils -private extension ItemLayoutAttributesDecoder { - - static func defaultLayoutAttributeAndConstant(for constrictorAttribute: ConstrictorAttribute, - with constantStruct: Constant) -> (attribute: NSLayoutConstraint.Attribute, constant: CGFloat) { - - let attribute: NSLayoutConstraint.Attribute - let constant: CGFloat - - switch constrictorAttribute { - case .top, .topGuide: - attribute = .top - constant = constantStruct.top - - case .bottom, .bottomGuide: - attribute = .bottom - constant = -constantStruct.bottom - - case .right, .rightGuide: - attribute = .right - constant = -constantStruct.right - - case .left, .leftGuide: - attribute = .left - constant = constantStruct.left - - case .leading, .leadingGuide: - attribute = .leading - constant = constantStruct.leading - - case .trailing, .trailingGuide: - attribute = .trailing - constant = -constantStruct.trailing - - case .centerX, .centerXGuide: - attribute = .centerX - constant = constantStruct.x - - case .centerY, .centerYGuide: - attribute = .centerY - constant = constantStruct.y - - case .width: - attribute = .width - constant = constantStruct.width - - case .height: - attribute = .height - constant = constantStruct.height - - case .none: - attribute = .notAnAttribute - constant = 0.0 - } - - return (attribute, constant) - } - - /** - Get an UILayoutGuide pinned to the viewController's safe edges. - - - parameters: - - viewController: UIViewController to get an UILayoutGuide pinned its edges - - - returns: - Safe UILayoutGuide. - */ - - static func safeLayoutGuide(for viewController: UIViewController) -> UILayoutGuide { - - let layoutGuide = UILayoutGuide() - viewController.view.addLayoutGuide(layoutGuide) - - NSLayoutConstraint.activate( - [ - NSLayoutConstraint( - item: layoutGuide, attribute: .top, relatedBy: .equal, - toItem: viewController.topLayoutGuide, attribute: .bottom, - multiplier: 1, constant: 0 - ), - NSLayoutConstraint( - item: layoutGuide, attribute: .bottom, relatedBy: .equal, - toItem: viewController.bottomLayoutGuide, attribute: .top, - multiplier: 1, constant: 0 - ), - NSLayoutConstraint( - item: layoutGuide, attribute: .leading, relatedBy: .equal, - toItem: viewController.view, attribute: .leading, - multiplier: 1, constant: 0 - ), - NSLayoutConstraint( - item: layoutGuide, attribute: .trailing, relatedBy: .equal, - toItem: viewController.view, attribute: .trailing, - multiplier: 1, constant: 0 - ) - ] - ) - - return layoutGuide - } -} diff --git a/Constrictor/Constrictor/Structs/LayoutItem.swift b/Constrictor/Constrictor/Structs/LayoutItem.swift new file mode 100644 index 0000000..352b70c --- /dev/null +++ b/Constrictor/Constrictor/Structs/LayoutItem.swift @@ -0,0 +1,56 @@ +// +// LayoutItem.swift +// Constrictor +// +// Created by Pedro Carrasco on 27/09/2018. +// Copyright © 2018 Pedro Carrasco. All rights reserved. +// + +import Foundation + +// MARK: - HeadLayoutItem +struct HeadLayoutItem { + + let attribute: NSLayoutConstraint.Attribute + let constant: CGFloat + + init(_ layoutItem: LayoutItem) { + self.attribute = layoutItem.attribute + self.constant = layoutItem.constant + } +} + +// MARK: - TailLayoutItem +struct TailLayoutItem { + + let element: Any? + let attribute: NSLayoutConstraint.Attribute + + init(_ layoutItem: LayoutItem) { + self.element = layoutItem.element + self.attribute = layoutItem.attribute + } +} + +// MARK: - LayoutItem +struct LayoutItem { + + let element: Any? + let attribute: NSLayoutConstraint.Attribute + let constant: CGFloat + + var asHead: HeadLayoutItem { + return HeadLayoutItem(self) + } + + var asTail: TailLayoutItem { + return TailLayoutItem(self) + } + + init(element: Any? = nil, attribute: NSLayoutConstraint.Attribute = .notAnAttribute, constant: CGFloat = 0.0) { + + self.element = element + self.attribute = attribute + self.constant = constant + } +} diff --git a/Constrictor/Constrictor/Extensions/Constrictable+Constrictor.swift b/Constrictor/Constrictor/Sugar/Constrictable+Constrictor.swift similarity index 100% rename from Constrictor/Constrictor/Extensions/Constrictable+Constrictor.swift rename to Constrictor/Constrictor/Sugar/Constrictable+Constrictor.swift diff --git a/Constrictor/Constrictor/Extensions/Constrictable+ConstrictorCenter.swift b/Constrictor/Constrictor/Sugar/Constrictable+ConstrictorCenter.swift similarity index 100% rename from Constrictor/Constrictor/Extensions/Constrictable+ConstrictorCenter.swift rename to Constrictor/Constrictor/Sugar/Constrictable+ConstrictorCenter.swift diff --git a/Constrictor/Constrictor/Extensions/Constrictable+ConstrictorEdges.swift b/Constrictor/Constrictor/Sugar/Constrictable+ConstrictorEdges.swift similarity index 100% rename from Constrictor/Constrictor/Extensions/Constrictable+ConstrictorEdges.swift rename to Constrictor/Constrictor/Sugar/Constrictable+ConstrictorEdges.swift diff --git a/Constrictor/ConstrictorTests/Tests/Enumerations/ItemLayoutAttributesDecoderTests.swift b/Constrictor/ConstrictorTests/Tests/Enumerations/LayoutItemFactoryTests.swift similarity index 85% rename from Constrictor/ConstrictorTests/Tests/Enumerations/ItemLayoutAttributesDecoderTests.swift rename to Constrictor/ConstrictorTests/Tests/Enumerations/LayoutItemFactoryTests.swift index 0216179..ce4e136 100644 --- a/Constrictor/ConstrictorTests/Tests/Enumerations/ItemLayoutAttributesDecoderTests.swift +++ b/Constrictor/ConstrictorTests/Tests/Enumerations/LayoutItemFactoryTests.swift @@ -1,5 +1,5 @@ //// -//// ItemLayoutrAttributesDecoderTests.swift +//// LayoutItemFactoryTests.swift //// ConstrictorTests //// //// Created by Pedro Carrasco on 28/05/2018. @@ -9,9 +9,10 @@ import XCTest @testable import Constrictor -// MARK: - ItemLayoutrAttributesDecoderTests -class ItemLayoutrAttributesDecoderTests: XCTestCase { +// MARK: - LayoutItemFactoryTests +class LayoutItemFactoryTests: XCTestCase { + private var parent: UIView! private var view: UIView! private var viewController: UIViewController! private var layoutGuide: UILayoutGuide! @@ -25,14 +26,19 @@ class ItemLayoutrAttributesDecoderTests: XCTestCase { override func setUp() { super.setUp() + parent = UIView() + view = UIView() viewController = UIViewController() layoutGuide = UILayoutGuide() + + parent.addSubview(view) + parent.addSubview(viewController.view) } } // MARK: - Tests -extension ItemLayoutrAttributesDecoderTests { +extension LayoutItemFactoryTests { // MARK: itemLayoutAttribute(for item: Constrictable?) -> (item: Any?, layoutAttribute: NSLayoutConstraint.Attribute) func testItemLayoutAttributeForUIViewNone() { @@ -317,7 +323,7 @@ extension ItemLayoutrAttributesDecoderTests { } // MARK: - Utils -private extension ItemLayoutrAttributesDecoderTests { +private extension LayoutItemFactoryTests { func test(_ view: UIView, for attribute: ConstrictorAttribute, @@ -325,21 +331,26 @@ private extension ItemLayoutrAttributesDecoderTests { constant: Constant = .zero, expectedConstant: CGFloat = 0.0) { - let itemAttributeTuple = ItemLayoutAttributesDecoder.itemLayoutAttribute(for: view, with: attribute, and: constant) - let resultItem = itemAttributeTuple.item - let resultAttribute = itemAttributeTuple.layoutAttribute + let items = LayoutItemFactory.makeLayoutItems(firstElement: parent, + secondElement: view, + firstAttribute: attribute, + secondAttribute: attribute, + constant: constant) if #available(iOS 11.0, *), ConstrictorAttribute.guidedAttributes.contains(attribute) { + let expectedItem = view.safeAreaLayoutGuide - XCTAssertEqual(resultItem as? UILayoutGuide, expectedItem) + XCTAssertEqual(items.tail.element as? UILayoutGuide, expectedItem) } else { + let expectedItem = view - XCTAssertEqual(resultItem as? UIView, expectedItem) + XCTAssertEqual(items.tail.element as? UIView, expectedItem) } - XCTAssertEqual(resultAttribute, expectedAttribute) - XCTAssertEqual(itemAttributeTuple.constant, expectedConstant) + XCTAssertEqual(items.head.attribute, expectedAttribute) + XCTAssertEqual(items.tail.attribute, expectedAttribute) + XCTAssertEqual(items.head.constant, expectedConstant) } func test(_ layoutGuide: UILayoutGuide, @@ -348,15 +359,12 @@ private extension ItemLayoutrAttributesDecoderTests { constant: Constant = .zero, expectedConstant: CGFloat = 0.0) { - let itemAttributeTuple = ItemLayoutAttributesDecoder.itemLayoutAttribute(for: layoutGuide, with: attribute, and: constant) - - let resultItem = itemAttributeTuple.item - let resultAttribute = itemAttributeTuple.layoutAttribute - + let item = LayoutItemFactory.makeLayoutItem(element: layoutGuide, + attribute: attribute, + constant: constant) - XCTAssertEqual(resultItem as? UILayoutGuide, layoutGuide) - XCTAssertEqual(resultAttribute, expectedAttribute) - XCTAssertEqual(itemAttributeTuple.constant, expectedConstant) + XCTAssertEqual(item.attribute, expectedAttribute) + XCTAssertEqual(item.constant, expectedConstant) } func test(_ viewController: UIViewController, @@ -365,41 +373,40 @@ private extension ItemLayoutrAttributesDecoderTests { constant: Constant = .zero, expectedConstant: CGFloat = 0.0) { - let itemAttributeTuple = ItemLayoutAttributesDecoder.itemLayoutAttribute(for: viewController, with: attribute, and: constant) - - let resultItem = itemAttributeTuple.item - let resultAttribute = itemAttributeTuple.layoutAttribute + let items = LayoutItemFactory.makeLayoutItems(firstElement: parent, + secondElement: viewController, + firstAttribute: attribute, + secondAttribute: attribute, + constant: constant) if #available(iOS 11.0, *), ConstrictorAttribute.guidedAttributes.contains(attribute) { + let expectedItem = viewController.view.safeAreaLayoutGuide - XCTAssertEqual(resultItem as? UILayoutGuide, expectedItem) + XCTAssertEqual(items.tail.element as? UILayoutGuide, expectedItem) + } else if attribute == .centerXGuide { XCTAssertNotNil(items.tail.element as? UILayoutGuide) + } else if attribute == .centerYGuide { XCTAssertNotNil(items.tail.element as? UILayoutGuide) } else if attribute == .topGuide { + let expectedItem = viewController.topLayoutGuide - guard let resultItem = resultItem as? UILayoutSupport else { return XCTFail() } + guard let resultItem = items.tail.element as? UILayoutSupport else { return XCTFail() } - XCTAssertEqual(resultAttribute, .bottom) XCTAssert(resultItem.isEqual(expectedItem)) return } else if attribute == .bottomGuide { + let expectedItem = viewController.bottomLayoutGuide - guard let resultItem = resultItem as? UILayoutSupport else { return XCTFail() } + guard let resultItem = items.tail.element as? UILayoutSupport else { return XCTFail() } - XCTAssertEqual(resultAttribute, .top) XCTAssert(resultItem.isEqual(expectedItem)) return - - } else if attribute == .centerXGuide { - XCTAssertNotNil(resultItem as? UILayoutGuide) - - } else if attribute == .centerYGuide { - XCTAssertNotNil(resultItem as? UILayoutGuide) } - XCTAssertEqual(resultAttribute, expectedAttribute) - XCTAssertEqual(itemAttributeTuple.constant, expectedConstant) + XCTAssertEqual(items.head.attribute, expectedAttribute) + XCTAssertEqual(items.tail.attribute, expectedAttribute) + XCTAssertEqual(items.head.constant, expectedConstant) } } diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 2b32506..b2de984 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -27,12 +27,6 @@ class ViewController: UIViewController { // Constraints -> Same dimensions of redview's superview redView.constrictEdgesToController(self, withinGuides: false) - // Or like it's done bellow - /* redView.constrictToViewController(attributes: .top, .bottom, .leading, .trailing) */ - - // Or if you want an offset of 50 over all edges - /*redView.constrictToViewController(attributes: .top, .bottom, .leading, .trailing, constant: 50.0) */ - // ** Blue View ** // Boilerplate blueView.backgroundColor = .blue @@ -40,7 +34,7 @@ class ViewController: UIViewController { // Constraints -> 75 width, 75 height and centered in viewcontroller's view - blueView.constrict(attributes: .width, with: .all(75)) + blueView.constrict(attributes: .width, .height, with: .all(75)) .constrictCenterInController(self) // ** Green View ** diff --git a/Example/Podfile.lock b/Example/Podfile.lock index b60199d..75e100b 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Constrictor (1.0.5) + - Constrictor (3.0.1) DEPENDENCIES: - Constrictor (from `..`) @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: ".." SPEC CHECKSUMS: - Constrictor: 3f3fdba52e35208b313d6442944c23c42f6b70ec + Constrictor: 9cc4316728fcd05fa09b7cf19ad03ed042472a0c PODFILE CHECKSUM: 378ecc6ae0c8cd6d7e4d84896a541255c3c32287 -COCOAPODS: 1.5.3 +COCOAPODS: 1.6.0.beta.1