App is crashing on NSLayoutConstraint when added programmatically - ios

I am getting the following error :
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout)
Basically I want to have a blue view that is 200h x 200w that is centered in the middle of the screen.
UIViewController.swift
override func loadView() {
super.loadView()
self.view = View()
}
Meanwhile in the View.swift
class View: UIView {
var blueView : UIView?
convenience init() {
// also tried UIScreen.mainScreen().bounds
self.init(frame:CGRectZero)
self.backgroundColor = UIColor.redColor()
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init:coder")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
func setupView() {
self.blueView = UIView()
self.blueView?.backgroundColor = UIColor.blueColor()
self.blueView?.translatesAutoresizingMaskIntoConstraints = false
// self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.blueView!)
let centerXConstraint = NSLayoutConstraint(
// object that we want to constrain
item: self.blueView!,
// the attribute of the item we want to constraint
attribute: NSLayoutAttribute.CenterX,
// how we want to relate this item with another item so most likely its parent view
relatedBy: NSLayoutRelation.Equal,
// this is the item that we are setting up the relationship with
toItem: self,
attribute: NSLayoutAttribute.CenterX,
// How much I want the CenterX of BlueView to Differ from the CenterX of the self
multiplier: 1.0,
constant: 0)
let centerYConstraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: self,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0)
// These work but the previous two don't
let widthContraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.Width,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0,
constant: 200)
let heightConstraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0,
constant: 200)
self.blueView?.addConstraints([widthContraint, heightConstraint, centerXConstraint, centerYConstraint])
}

Height and width constraints belong to the view they pertain to. Centering and positioning belong to that view's parent so you must add these two to the parent, not the view itself.

Related

Nested #IBDesignables using xibs?

I'm trying to create a component-based system on iOS, and I'd like to do the following:
Create a "PaddedView" component that has 8px of space around any added child components, like a container type component.
Add another IBDesignable view into this PaddedView on a storyboard, and see both render.
Is this possible?
Right now, I'm using the following superclass for all IBDesignable components to load their views from xibs:
import Foundation
import UIKit
#IBDesignable
class SKDesignableView: UIView {
var view: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.loadXib()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.loadXib()
}
func loadXib() {
self.view = self.viewFromXib()
self.view!.frame = self.bounds
self.view!.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
self.addSubview(self.view!)
}
func viewFromXib() -> UIView {
let bundle = UINib(nibName: String(describing: self.getType()), bundle: Bundle(for: self.getType()))
let views = bundle.instantiate(withOwner: self, options: nil)
return views.first as! UIView
}
func getType() -> AnyClass {
fatalError()
}
}
How do I create placeholders for other IBDesignables?
The view initially contains the children, so add a container view as a subview to any component that needs children.
func loadXib() {
var subview: UIView? = nil
if self.subviews.count > 0 {
subview = self.subviews[0]
}
subview?.removeFromSuperview()
self.view = self.viewFromXib()
self.view!.frame = self.bounds
self.view!.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
if let subview = subview {
let lConstraint = NSLayoutConstraint(item: subview, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal,
toItem: self.view!, attribute: NSLayoutAttribute.leading, multiplier: 1, constant: 8)
let rConstraint = NSLayoutConstraint(item: subview, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal,
toItem: self.view!, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: -8)
let tConstraint = NSLayoutConstraint(item: subview, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal,
toItem: self.view!, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 8)
let bConstraint = NSLayoutConstraint(item: subview, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal,
toItem: self.view!, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: -8)
self.view!.addSubview(subview)
self.view!.addConstraints([lConstraint, rConstraint, tConstraint, bConstraint])
}
self.addSubview(self.view!)
}
This approach can be generalized with tags etc to add multiple subviews.

Constraints programmatically for custom UIView in Dynamic ViewController

I'm creating new UIViewController dynamycally using this code
#IBAction func newVCBtnPressed(_ sender: Any) {
let controller = DynamicVC()
show(controller, sender: sender)
}
In the new UIViewController I'm using this code for creation of the new UIView:
override func loadView() {
view = UIView()
view.backgroundColor = .lightGray
}
In result I have view with .lightGray backgroundcolor.
I want to add custom UIView and setup the constraints programmatically, and in result i want UIView with following constraints:
top: 0
bottom:(view.frame.height*0.9)
leading:0
trailing:(view.frame.width*0.15)
width:(view.frame.width*0.85)
height:(view.frame.height*0.1)
Example:
Here is my code:
topMenuView = UIView()
topMenuView.backgroundColor = .red
view.addSubview(topMenuView)
topMenuView.translatesAutoresizingMaskIntoConstraints = false
setupConstraints(item: topMenuView, topC: 0, topToItem: view, bottomC: (view.frame.height*0.9), bottomToItem: view, widthC: (view.frame.width*0.85), heightC: (view.frame.height*0.1), leadingCon: 0, trailingCon: (view.frame.width*0.15))
I'm using this constructed function for constraints:
func setupConstraints(item:UIView, topC:CGFloat, topToItem:UIView, bottomC:CGFloat, bottomToItem:UIView, widthC:CGFloat, heightC:CGFloat, leadingCon:CGFloat, trailingCon:CGFloat) {
let topConstraint = NSLayoutConstraint(item: item, attribute: .top, relatedBy: .equal, toItem: topToItem, attribute: .bottom, multiplier: 1, constant: topC)
let bottomConstraint = NSLayoutConstraint(item: item, attribute: .bottom, relatedBy: .equal, toItem: bottomToItem, attribute: .top, multiplier: 1, constant: bottomC)
let widthConstraint = NSLayoutConstraint(item: item, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: widthC)
let heightConstraint = NSLayoutConstraint(item: item, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: heightC)
let leading = NSLayoutConstraint(item: item,attribute: .leading,relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: leadingCon)
let trailing = NSLayoutConstraint(item: item,attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin,multiplier: 1.0,constant: trailingCon)
view?.addConstraints([topConstraint, bottomConstraint, widthConstraint, heightConstraint, leading, trailing])
NSLayoutConstraint.activate([topConstraint, bottomConstraint, widthConstraint, heightConstraint, leading, trailing])
}
But in the result i receive only UIView with gray background, the new UIView with red background doesn't appears.
What I'm doing wrong???
You should only specify bottom OR height and width OR trailing, otherwise you are going to get conflicts here.
see playground:
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let red = UIView()
red.backgroundColor = .red
view.addSubview(red)
red.translatesAutoresizingMaskIntoConstraints = false
red.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
red.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
red.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85).isActive = true
red.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true
}
}
PlaygroundPage.current.liveView = ViewController()

How to implement a custom Swift object with constraints?

Summary:
I want to implement my own UIView object with specific layout constraints utilizing Swift 3. I'm not sure how to configure the layout constraints in the object I'm customizing. I was thinking of passing in a reference to the super view to append the layout constraints onto.
Question:
How can I implement my own custom user interface object while also maintaining that objects layout constraints in the model instead of the view?
Code:
Custom View:
import Foundation
import UIKit
class CustomView: UIView {
var header: UIView?
var users: [Array<OtherObject>]?
init() {
super.init(frame: UIScreen.main.bounds);
//for debug validation
print("My Custom Init");
return;
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented");
}
func configureTitleHeader(){
header = UIView()
}
func configureConstraints(superView: UIView){
self.configureTitleHeader()
let titleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.header!, attribute: .left, relatedBy: .equal, toItem: superView, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.header!, attribute: .right, relatedBy: .equal, toItem: superView, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.header!, attribute: .top, relatedBy: .equal, toItem: superView, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.header!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
]
superView.addConstraints(titleConstraints)
}
}
UINavigationController:
override func viewDidLoad() {
super.viewDidLoad()
let customView = CustomView()
customView.configureConstraints(superView: self.view)
self.view.addSubview(customView)
}

Custom view created with Xib not working in Interface Builder

I've created a custom view with the help of an .Xib file. When I create the view programmatically and add it to my ViewController it works just fine. But if I create a UIView in Interface Builder and set the class to my CustomView class and run it, it doesn't show up.
This is the Code in my CustomView class:
#IBOutlet var view: UIView!
init() {
super.init(frame:CGRect.zero)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
func setup() {
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
view.backgroundColor = UIColor(red: 10.0/255.0, green: 30.0/255.0, blue: 52.0/255.0, alpha: 1.0)
view.translatesAutoresizingMaskIntoConstraints = false
view.isUserInteractionEnabled = true
}
func presentInView(superView:UIView) {
superView.addSubview(view)
// Define Constraints
let height = NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 90.0)
view.addConstraint(height)
let topConstraint = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: superView, attribute: .top, multiplier: 1.0, constant: 0.0)
let leftConstraint = NSLayoutConstraint(item: view, attribute: .left, relatedBy: .equal, toItem: superView, attribute: .left, multiplier: 1.0, constant: 0.0)
let rightConstraint = NSLayoutConstraint(item: view, attribute: .right, relatedBy: .equal, toItem: superView, attribute: .right, multiplier: 1.0, constant: 0.0)
superView.addConstraints([topConstraint,leftConstraint, rightConstraint])
}
In the .Xib file I set the class for the Files Owner to CustomView and the connect the IBOutlet view to the main view in the .Xib.
In my ViewController I did this to add the CustomView to it:
let customView = CustomView()
customView.presentInView(superView: self.view)
When I add a UIView in Interface Builder it should work just as it does when I do it programmatically.
Did you assign CustomView Class to you uiview in interface Builder.
The Problem was the size of the view, the IBOutlet that I connected to my view in the .Xib file. When I created my CustomView in code, I also called the presentInView method, which I thought was unnecessary when created in the Interface Builder because I set the constraints there. But I forgot, that the constraints that I set in Interface Builder are for the class itself and not for its view outlet. So the view needs constraints to hold it in its superview, which is not the ViewController.view but CustomView. Thats why I also have to write self.addSubview(view)
With these changes it works. Here is the final code:
func setup() {
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
view.backgroundColor = UIColor(red: 10.0/255.0, green: 30.0/255.0, blue: 52.0/255.0, alpha: 1.0)
view.translatesAutoresizingMaskIntoConstraints = false
view.isUserInteractionEnabled = true
self.addSubview(view)
let topConstraint = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let leftConstraint = NSLayoutConstraint(item: view, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0.0)
let rightConstraint = NSLayoutConstraint(item: view, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
self.addConstraints([topConstraint,leftConstraint, rightConstraint, bottomConstraint])
}
The presentInView method is no longer needed.
Hope it helps if someone else has a problem like this.

How to create a custom UIView which has just a button and then can be used throughout the app placing it in different positions?

I have already created a circular button which is a custom UIView.Here's the code:
class HelpTips: UIView {
weak var hotSpot: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
let strongHotSpot = UIButton()
hotSpot = strongHotSpot
self.addSubview(strongHotSpot)
hotSpotOne.translatesAutoresizingMaskIntoConstraints = false
hotSpotOne.backgroundColor = UIColor.TRLMHelpTipYellowColor()
hotSpotOne.layer.borderColor = UIColor.TRLMHelpTipStrokeColor().CGColor
hotSpotOne.layer.borderWidth = 1
let horizontalConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: -1)
let verticalConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 16)
let widthConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 40)
let heightConstraint = NSLayoutConstraint(item: hotSpot, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 40)
self.addConstraints([verticalConstraint, horizontalConstraint, widthConstraint, heightConstraint])
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Now this same button is used throughout the app at several places but it's placed at different positions. So each View Controller will make use of that UIView.
So technically the look of the button remains the same but the constraints for that button keep on changing depending on it's position. I want to follow DRY(Don't repeat yourself) technique here.
I have done this kind of thing before but the code was being repeated several times and was not efficient. How to go about this?
Any help is appreciated. Thanks!
You could create a custom navigation controller and store the view as a property in it. Every view controller will have access to the navigation controller, so they can just reference that property and time you need to use the view. Should keep it DRY.
Create a custom view using an xib. Add your UIButton as subview.
This tutorial will be helpful.

Resources