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.
Related
So I have created a Progress Indicator View that I am showing on API calls. I have created a custom UIView Class for it.
Now, everything works fine. But the position of view should be in centre but it's not.
I think I have the constraints right but still its not working.
Here is my code:
import Foundation
import UIKit
import UICircularProgressRing
import HGRippleRadarView
class ProgressIndicator : UIView {
#IBOutlet weak var contentView : UIView!
#IBOutlet weak var progressView : UICircularProgressRing!
#IBOutlet weak var logoContainerView : UIView!
#IBOutlet weak var rippleView : RippleView!
static let shared = ProgressIndicator()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed("ProgressIndicator", owner: self, options: nil)
addSubview(contentView)
}
public func show(controller : UIViewController) {
setupLoadingView(controller : controller)
}
public func hide() {
removeLoadingView()
}
private func setupLoadingView(controller : UIViewController) {
controller.view.addSubview(self)
// adding contrints on main view
let leadingConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: controller.view, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: controller.view, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: controller.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: controller.view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0)
controller.view.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
// adding constraints on content view
let leadingConstraint1 = NSLayoutConstraint(item: self.contentView!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1, constant: 0)
let trailingConstraint1 = NSLayoutConstraint(item: self.contentView!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1, constant: 0)
let topConstraint1 = NSLayoutConstraint(item: self.contentView!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
let bottomConstraint1 = NSLayoutConstraint(item: self.contentView!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0)
self.addConstraints([leadingConstraint1, trailingConstraint1, topConstraint1, bottomConstraint1])
self.setNeedsLayout()
self.reloadInputViews()
self.layoutIfNeeded()
}
}
Here is the result m getting:
And here is the xib file screenshot
You have added constraints but didnt set translatesAutoresizingMaskIntoConstraints to false.
self.translatesAutoresizingMaskIntoConstraints = false
self.contentView.translatesAutoresizingMaskIntoConstraints = false
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()
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)
}
Below I have included some code for you to checkout. I am trying to take a custom UIView and add another custom subview to it. This subview should be constrained to the parent view in such a way it essentially just lays on top with the same dimensions and the parent just acts as a wrapper.
I have tried using the NSLayoutConstraint and failed miserable so far. The view never actually shows up. I have a left, right, bottom, and top constraint which should line up with the parent view.
The first ask I have is that someone please explain and or correct my logic when using the following method. The item param I figured is the actual view you want to set a constraint for (customViewChild). The attribute is to say that I want the left edge of my customViewChild to be used for this constraint. The relatedBy seems pretty straight forward although I could be wrong, and then finally the toItem points to self which is my CustomViewParent which also has a .left attribute to say that I want the left edge of my child and parent to line up. Is this logic flawed or am I doing something else wrong?
NSLayoutConstraint(item: customViewChild!,
attribute: .left,
relatedBy: .equal,
toItem: self,
attribute: .left,
multiplier: 1.0,
constant: 0.0)
I know the following example could very easily be done with IB, but I am trying to understand NSLayoutConstraint, so please provide answers regarding that. And lastly, if anyone could actually correct this code so I have a working example, that would be awesome.
class CustomViewParent: UIView {
var customViewChild: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
setConstraints()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setConstraints()
}
func setConstraints() {
customViewChild = UIView()
addSubview(customViewChild!)
customViewChild?.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = NSLayoutConstraint(item: customViewChild!,
attribute: .left,
relatedBy: .equal,
toItem: self,
attribute: .left,
multiplier: 1.0,
constant: 0.0).isActive = true
let rightConstraint = NSLayoutConstraint(item: customViewChild!,
attribute: .right,
relatedBy: .equal,
toItem: self,
attribute: .right,
multiplier: 1.0,
constant: 0.0).isActive = true
let topConstraint = NSLayoutConstraint(item: customViewChild!,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: 0.0).isActive = true
let bottomConstraint = NSLayoutConstraint(item: customViewChild!,
attribute: .bottom,
relatedBy: .equal,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: 0.0).isActive = true
customViewChild.addConstraint([leftConstraint, rightConstraint, topConstraint, bottomConstraint]);
}
}
You may find this a bit easier, and more readable...
func setConstraints() {
if customViewChild == nil {
customViewChild = UIView()
addSubview(customViewChild!)
customViewChild?.translatesAutoresizingMaskIntoConstraints = false
customViewChild?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
customViewChild?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
customViewChild?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
customViewChild?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
}
Three things:
You don't need to use addConstraint. Just set isActive to true for the constraints.
It doesn't make sense to both set isActive to true and to assign the result of that to a constant. Setting the isActive property doesn't return the NSLayoutConstraint. It returns ().
You should use .leading and .trailing instead of .left and .right.
With these changes, the following should work:
func setConstraints() {
customViewChild = UIView()
addSubview(customViewChild!)
customViewChild?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: customViewChild!,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1.0,
constant: 0.0).isActive = true
NSLayoutConstraint(item: customViewChild!,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: 1.0,
constant: 0.0).isActive = true
NSLayoutConstraint(item: customViewChild!,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: 0.0).isActive = true
NSLayoutConstraint(item: customViewChild!,
attribute: .bottom,
relatedBy: .equal,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: 0.0).isActive = true
}
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.