I have the following classes:
/**
* This is basically an empty class and exists for the purpose of being able to
* define IBOutlets as BaseHeading, regardless of which specific heading is used in IB.
*/
class BaseHeading: UIView { }
/**
* This class has a .xib where the layout is defined.
* This view should be used as navigationBar for modally-presented ViewControllers.
*/
class HeadingIconRight: BaseHeading { } // This class has a .xib.
/**
* This class has a .xib where the layout is defined.
* This view should be used as navigationBar for pused ViewControllers.
*/
class HeadingIconLeft: BaseHeading { } // This class has a .xib.
In MyViewController
's .xib, I have a custom UIView
of type BaseHeading
. In its Swift-file I have an @IBOutlet private weak var heading: BaseHeading!
, connected to this.
In the Swift file I know which specific header (HeadingIconRight
/ HeadingIconLeft
) should be used, I'm just unsure how to achieve this in code.
What I want:
IBOutlet
's reference to be that viewWhat I've tried so far:
let headingIndex = self.view.subviews.firstIndex { $0 == self.heading }
let newHeading = HeadingIconRight()
// Loop through 'old' heading's constraints and convert them to the new heading.
self.heading.constraints.forEach { constraint in
let firstItem: AnyObject? = constraint.firstItem as! AnyHashable == self.heading as AnyHashable ? newHeading : constraint.firstItem
let secondItem: AnyObject? = constraint.secondItem as! AnyHashable == self.heading as AnyHashable ? newHeading : constraint.secondItem
newHeading.addConstraint(
NSLayoutConstraint(item: firstItem,
attribute: constraint.firstAttribute,
relatedBy: constraint.relation,
toItem: secondItem,
attribute: constraint.secondAttribute,
multiplier: constraint.multiplier,
constant: constraint.constant)
)
}
self.heading.removeFromSuperview()
self.view.insertSubview(newHeading, at: headingIndex!)
self.heading = newHeading
Of course I'm going to try more, but instead of building something very complicated I'd like to have some guidance in the right direction to come to a good solution.
If it would somehow be possible to override the lifecycle function where the Xib's views are initialized, and replace this one with the specific header, that would be a nice solution (if it doesn't violate the UIViewController API).
I went with @Paulw11's suggestion and created the view at runtime, in code.
Instead of adding a BaseHeading
-view in IB, I constrain the next top-most view to the top of the ViewController
and make a reference to this constraint (e.g. topMostConstraint
).
I added the following function to BaseHeading
:
public func constrainToTop(in container: UIView, above otherView: UIView, bottomMargin: CGFloat = 0, disableConstraint: NSLayoutConstraint? = nil)
{
container.addSubview(self)
self.translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: otherView.topAnchor, constant: -bottomMargin).isActive = true
disableConstraint?.isActive = false
}
What this does is:
self
to the containerViewIn my ViewController
's viewDidLoad()
I can now do the following:
public class MyViewController: UIViewController
{
private var heading: BaseHeading?
@IBOutlet private weak var topMostConstraint: NSLayoutConstraint!
@IBOutlet private weak var anyOtherView: UIStackView!
override func viewDidLoad()
{
super.viewDidLoad()
self.heading = HeadingIconRight() // Or any other BaseHeading-subclass.
self.heading?.constrainToTop(in: self.view,
above: self.anyOtherView,
bottomMargin: 16,
disableConstraint: self.topMostConstraint)
}
}
This seems to do the job, and ended up less complicated than I anticipated.