Skip to content

Commit

Permalink
Added documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Bibin.Joseph committed Apr 5, 2023
1 parent 3599664 commit 7287baa
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 17 deletions.
27 changes: 25 additions & 2 deletions Sources/FloorPlan/Array+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,36 @@

import UIKit

/// This functions are added as an extention to Array<UIView.Plan.Item> for conveniance.
public extension Array where Element == UIView.Plan.Item {
/// Creates and activates a constraint from current view's specified attributes to corresponding superview's attributes. Constant is zero.
/// - Returns: Array of plan items of each attribute after constraint creation
@discardableResult func equalToSuperView() -> [UIView.Plan.Item] {
forEach { $0.equalToSuperView() }
return self
}

/// Creates and activates a constraint from current view's specified attributes anchors to corresponding safeArea anchors. Constant is zero.
/// - Returns: Array of plan items of each attribute after constraint creation
@discardableResult func equalToSafeArea() -> [UIView.Plan.Item] {
forEach { $0.equalToSafeArea() }
return self
}

/// Creates and activates constraints from current view's attributes to specified attributes of another view/safeArea.
/// Please Note: There order current views attributes and order of another view's attributes/safeAre should correspondingly match.
/// - Parameter items: Plan items to which constraint relation needs to be created.
/// - Returns: Array of plan items of each attribute after constraint creation
@discardableResult func equalTo(_ items: [UIView.Plan.Item]) -> [UIView.Plan.Item] {
assert(count == items.count, "Count mismatch. Plan cannot be built")
for (index, item) in self.enumerated() { item.equalTo(items[index]) }
return self
}

/// Creates and activates width and height constraints on current view based on the specified size.
/// Please Note: Use of this API for attributes other than height and width is not recommended.
/// - Parameter size: The size with which width and height constraints needs to be created
/// - Returns: Array of plan items (width and height)
@discardableResult func equalTo(_ size: CGSize) -> [UIView.Plan.Item] {
let widthPlanItem = first { $0.firstAttribute == .width }
let heightPlanItem = first { $0.firstAttribute == .height }
Expand All @@ -33,18 +46,29 @@ public extension Array where Element == UIView.Plan.Item {
return self
}

/// Updates the constraint constant for each plan item in the array.
/// Please Note: For attributes .right, .trailing, .bottom, constant will be automatically negated.
/// i.e, if the given constant is 8, for .right, .trailing, .bottom, the negated value will be -8.
/// This negation is intentional and purely for convenience.
/// - Parameter constant: The constant that needs to be set.
/// - Returns: Array of plan items for each attribute after constant is updated
@discardableResult func offset(byConstant constant: CGFloat) -> [UIView.Plan.Item] {
forEach { $0.offset(byConstant: constant) }
return self
}

/// Recreates the constraint with mutiplier for each plan item in the array..
/// Please Note: Multipler for safeArea attributes except .width and .height is not supported.
/// - Parameter multiplier: The multiplier that needs to be set to constraints.
/// - Returns: Array of plan items for each attribute after mutiplier is updated
@discardableResult func offset(byMultiplier multiplier: CGFloat) -> [UIView.Plan.Item] {
forEach { $0.offset(byMultiplier: multiplier) }
return self
}
}

public extension Array where Element == UIView.Plan.Item {
/// This is purely for conveniance. Searches for the attribute in the given collection and returns the first match.
public extension Collection where Element == UIView.Plan.Item {
var left: UIView.Plan.Item? { first { $0.firstAttribute == .left } }
var right: UIView.Plan.Item? { first { $0.firstAttribute == .right } }
var top: UIView.Plan.Item? { first { $0.firstAttribute == .top } }
Expand All @@ -56,4 +80,3 @@ public extension Array where Element == UIView.Plan.Item {
var centerX: UIView.Plan.Item? { first { $0.firstAttribute == .centerX } }
var centerY: UIView.Plan.Item? { first { $0.firstAttribute == .centerY } }
}

20 changes: 14 additions & 6 deletions Sources/FloorPlan/UIView+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,32 @@
import UIKit

public extension UIView {
/// Holds the reference to the plan for all views in a dictionary.
/// Key: String - Address of the view
/// Value: Plan for the view.
private static var _plan = [String: Plan]()

/// The address of the view.
private var address: String { String(format: "%p", unsafeBitCast(self, to: Int.self)) }

/// Plan of the view.
/// If a view does not have a plan, a new plan will be created and returned.
var plan: Plan {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
var pl = UIView._plan[tmpAddress]
var pl = UIView._plan[address]
if pl == nil {
pl = Plan(self)
self.plan = pl!
}
return pl!
}
set(newValue) {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
UIView._plan[tmpAddress] = newValue
}
set { UIView._plan[address] = newValue }
}

/// Add a given view to the current view as subview.
/// - Parameters:
/// - view: view to add as subview to current view
/// - layout: Add all your plans for the view in this block. This will be executed immediatly after given view is added as subview.
func addSubview<view: UIView>(_ view: view, layout: (Plan) -> Void) {
self.addSubview(view)
layout(view.plan)
Expand Down
46 changes: 44 additions & 2 deletions Sources/FloorPlan/UIView.Plan.Item+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

import UIKit

/// Conveniance methods for creating constraints
public extension UIView.Plan.Item {
/// Creates and activates a constraint from current view's specified attribute to corresponding superview's attributes. Constant is zero.
/// - Returns: Plan item of the attribute after constraint creation
@discardableResult func equalToSuperView() -> UIView.Plan.Item {
secondAttribute = firstAttribute
relatedToView = plan?.view.superview
Expand All @@ -17,6 +20,8 @@ public extension UIView.Plan.Item {
return self
}

/// Creates and activates a constraint from current view's specified attribute anchor to corresponding safeArea anchor. Constant is zero.
/// - Returns: Plan item of the attribute after constraint creation
@discardableResult func equalToSafeArea() -> UIView.Plan.Item {
secondAttribute = firstAttribute
relatedToView = plan?.view.superview
Expand All @@ -27,6 +32,9 @@ public extension UIView.Plan.Item {
return self
}

/// Creates and activates constraints from current view's attribute to specified attribute of another view/safeArea.
/// - Parameter item: Plan items to which constraint relation needs to be created.
/// - Returns: Plan item of attribute after constraint creation
@discardableResult func equalTo(_ item: UIView.Plan.Item) -> UIView.Plan.Item {
relatedToView = item.plan?.view
secondAttribute = item.firstAttribute
Expand All @@ -37,6 +45,9 @@ public extension UIView.Plan.Item {
return self
}

/// Creates and activates constraints from current view's attribute to specified attribute of another view/safeArea with lessThanOrEqual relation.
/// - Parameter item: Plan items to which constraint relation needs to be created.
/// - Returns: Plan item of attribute after constraint creation
@discardableResult func lessThanOrEqualTo(_ item: UIView.Plan.Item) -> UIView.Plan.Item {
relatedToView = item.plan?.view
secondAttribute = item.firstAttribute
Expand All @@ -47,6 +58,9 @@ public extension UIView.Plan.Item {
return self
}

/// Creates and activates constraints from current view's attribute to specified attribute of another view/safeArea with greaterThanOrEqual relation.
/// - Parameter item: Plan items to which constraint relation needs to be created.
/// - Returns: Plan item of attribute after constraint creation
@discardableResult func greaterThanOrEqualTo(_ item: UIView.Plan.Item) -> UIView.Plan.Item {
relatedToView = item.plan?.view
secondAttribute = item.firstAttribute
Expand All @@ -57,6 +71,11 @@ public extension UIView.Plan.Item {
return self
}

/// Updates the constraint constant of the plan item constraint for width and height.
/// Please Note: This function is intended for attributes .width and .height.
/// For other attributes, use offset(byConstant:)
/// - Parameter constant: The constant that needs to be set.
/// - Returns: Plan item of attribute after constraint is updated
@discardableResult func equalTo(_ constant: CGFloat) -> UIView.Plan.Item {
secondAttribute = .notAnAttribute
constraint = constraint ?? createConstraint(constant: constant)
Expand All @@ -65,13 +84,22 @@ public extension UIView.Plan.Item {
return self
}

/// Updates the constraint constant of the plan item constraint for attributes except width and height.
/// Please Note: For attributes .right, .trailing, .bottom, constant will be automatically negated.
/// i.e, if the given constant is 8, for .right, .trailing, .bottom, the negated value will be -8.
/// This negation is intentional and purely for convenience.
/// - Parameter constant: The constant that needs to be set.
/// - Returns: Plan item of attribute after constraint is updated
@discardableResult func offset(byConstant constant: CGFloat) -> UIView.Plan.Item {
assert(constraint != nil, "Constraint Not found. Offset cannot be set")
assert(firstAttribute != nil, "Constraint Not found. Offset cannot be set")
constraint?.constant = constant * ([.trailing, .right, .bottom].contains(firstAttribute!) ? -1 : 1)
return self
}

/// Removes and recreate the constraint with multiplier.
/// - Parameter multiplier: The constant multiplied with the attribute on the right side of the constraint as part of getting the modified attribute.
/// - Returns: Plan item of attribute after constraint is recreated.
@discardableResult func offset(byMultiplier multiplier: CGFloat) -> UIView.Plan.Item {
assert(constraint != nil, "Constraint Not found. Offset multiplier cannot be set")
removeExistingConstraint()
Expand All @@ -80,20 +108,30 @@ public extension UIView.Plan.Item {
return self
}

/// Sets the priority of the generated constraint. If a constraint's priority level is less than required, then it is optional. Higher priority constraints are met before lower priority constraints.
/// - Parameter priority: The priority that needs to be set to constraint
/// - Returns: Plan item of attribute after constraint is updated.
@discardableResult func priority(_ priority: UILayoutPriority) -> UIView.Plan.Item {
assert(constraint != nil, "Constraint Not found. Priority cannot be set")
constraint?.priority = priority
return self
}

/// Creates the constraints from current view to specified view/safeArea.
/// If safeArea is set, then this function will ignore relatedToView while creating constraint.
/// - Parameters:
/// - relation: A relation between the first attribute of current view and modified attribute of secondView.
/// - multiplier: The constant multiplied with the attribute on the right side of the constraint as part of getting the modified attribute.
/// - constant: The constant added to the multiplied attribute value on the right side of the constraint to yield the final modified attribute.
/// - Returns: Created constriant
internal func createConstraint(relation: NSLayoutConstraint.Relation = .equal,
multiplier: CGFloat = 1,
constant: CGFloat = 0) -> NSLayoutConstraint? {
assert(plan?.view != nil, "View not found")
assert(firstAttribute != nil, "FirstAttribute not found")
assert(secondAttribute != nil, "SecondAttribute not found")
if safeAreaLayoutGuides == nil {
return NSLayoutConstraint(item: plan!.view,
return NSLayoutConstraint(item: plan!.view!,
attribute: firstAttribute!,
relatedBy: relation,
toItem: relatedToView,
Expand Down Expand Up @@ -129,17 +167,21 @@ public extension UIView.Plan.Item {
return nil
}

/// Calling this function will remove the constraint from the view alongside removing the plan item from .current propery of the plan.
internal func removeExistingConstraint() {
guard constraint != nil else { return }
constraint?.isActive = false
plan?.view.removeConstraint(constraint!)
plan?.current.remove(self)
constraint = nil
}

/// Calling this function will prepare the view and activate the generated constraint.
/// Activated constraint will be available in .current propery of the plan.
func build() {
assert(constraint != nil, "Constraint is not created. Cannot build.")
plan?.view.translatesAutoresizingMaskIntoConstraints = false
constraint?.isActive = true
plan?.current.append(self)
plan?.current.insert(self)
}
}
18 changes: 13 additions & 5 deletions Sources/FloorPlan/UIView.Plan.Item.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,25 @@
import UIKit

public extension UIView.Plan {
/// An object that represents all information necessary to create a constraint.
class Item {
public internal(set) var plan: UIView.Plan?
/// Plan object of view on which constraints will be applied on.
public internal(set) weak var plan: UIView.Plan?
/// First attributes of the constraint relation
public internal(set) var firstAttribute: NSLayoutConstraint.Attribute?
/// Second attributes of the constraint relation
public internal(set) var secondAttribute: NSLayoutConstraint.Attribute?
/// UIView to which the constraint relation from current view is established.
/// This property will be overlooked if safeAreaLayoutGuides are specified.
public internal(set) var relatedToView: UIView?
/// safeAreaLayoutGuides to which relation is established.
/// If set, relatedToView will be ignored during constraint creation.
public internal(set) var safeAreaLayoutGuides: UILayoutGuide?
/// Constraint that is currently generated for the view
public var constraint: NSLayoutConstraint?

public var constraint: NSLayoutConstraint? {
didSet { constraint?.isActive = true }
}

/// Initialises a plan item with a UIViews plan
/// - Parameter plan: Plan for a UIView
internal init(with plan: UIView.Plan) {
self.plan = plan
}
Expand Down
37 changes: 35 additions & 2 deletions Sources/FloorPlan/UIView.Plan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,74 @@
import UIKit

public extension UIView {
/// An object that holds references to the current view plan items
class Plan {
public let view: UIView
public var current: [UIView.Plan.Item] = []
public weak var view: UIView!
public var current: Set<UIView.Plan.Item> = []
public internal(set) var safeAreaLayoutGuides: UILayoutGuide?

/// Initialises the plan for a view
/// - Parameter view: view for which the the constraint needs to be added.
internal init(_ view: UIView) { self.view = view }
}
}

public extension UIView.Plan {
/// Creates a plan item for a specific attributes.
/// - Parameter attribute: Attribute of the view.
/// - Returns: A plan item that has all the necessary information to create a constraint.
internal func createPlanItem(for attribute: NSLayoutConstraint.Attribute) -> UIView.Plan.Item {
let planItem = UIView.Plan.Item(with: self)
planItem.firstAttribute = attribute
planItem.safeAreaLayoutGuides = safeAreaLayoutGuides
return planItem
}

/// Returns plan after capturing the safeArea layout guides.
var safeArea: UIView.Plan {
safeAreaLayoutGuides = view.safeAreaLayoutGuide
return self
}

/// Returns a new plan item for left attribute
var left: UIView.Plan.Item { createPlanItem(for: .left) }
/// Returns a new plan item for right attribute
var right: UIView.Plan.Item { createPlanItem(for: .right) }
/// Returns a new plan item for top attribute
var top: UIView.Plan.Item { createPlanItem(for: .top) }
/// Returns a new plan item for bottom attribute
var bottom: UIView.Plan.Item { createPlanItem(for: .bottom) }
/// Returns a new plan item for leading attribute
var leading: UIView.Plan.Item { createPlanItem(for: .leading) }
/// Returns a new plan item for trailing attribute
var trailing: UIView.Plan.Item { createPlanItem(for: .trailing) }
/// Returns a new plan item for width attribute
var width: UIView.Plan.Item { createPlanItem(for: .width) }
/// Returns a new plan item for height attribute
var height: UIView.Plan.Item { createPlanItem(for: .height) }
/// Returns a new plan item for centerX attribute
var centerX: UIView.Plan.Item { createPlanItem(for: .centerX) }
/// Returns a new plan item for centerY attribute
var centerY: UIView.Plan.Item { createPlanItem(for: .centerY) }
/// Returns a collection of new plan items for leading, trailing, top and bottom attributes
var edges: [UIView.Plan.Item] { [leading, trailing, top, bottom] }
/// Returns a collection of new plan items for leading and trailing attributes
var horizontalEdges: [UIView.Plan.Item] { [leading, trailing] }
/// Returns a collection of new plan items for top and bottom attributes
var verticalEdges: [UIView.Plan.Item] { [top, bottom] }
/// Returns a collection of new plan items for width and height attributes
var size: [UIView.Plan.Item] { [width, height] }
/// Returns a collection of new plan items for centerX and centerY attributes
var center: [UIView.Plan.Item] { [centerX, centerY] }
}

/// Hashable Conformance
extension UIView.Plan.Item: Hashable {
public static func == (lhs: UIView.Plan.Item, rhs: UIView.Plan.Item) -> Bool {
lhs.constraint == rhs.constraint
}

public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}

0 comments on commit 7287baa

Please sign in to comment.