Adding constraints in awakeFromNib() method - swift

I have two custom views in my project which needs to be zoomed in the exact same proportion. So I decided to use UIScrollView for that and it fits perfectly.
I decided to develop a very simple class inherited from UIScrollView and inside of it I initialized all views structure. That way I'm avoiding any construction steps in the NIB file and my class can be used just by adding one view. But I faced an issue already on the stage of adding contentView.
Here's my class:
final class PlayerContentView: UIScrollView {
fileprivate var contentView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .clear
setupScrollProperties()
setupContentView()
}
private func setupScrollProperties()
{
self.translatesAutoresizingMaskIntoConstraints = false
self.minimumZoomScale = 1.0
self.maximumZoomScale = 2.0
self.contentSize = frame.size
self.delegate = self
}
private func setupContentView()
{
contentView = UIView(frame: self.frame)
contentView.backgroundColor = .red
self.addSubview(contentView)
CommonSwiftUtility.setSideConstraints(superview: self, view: contentView)
CommonSwiftUtility.setSizeConstraints(superview: self, view: contentView)
}
func requireToFail(_ gestureRecognizer: UIPanGestureRecognizer) {
self.panGestureRecognizer.require(toFail: gestureRecognizer)
} }
And here're methods for adding constraints:
static func setSideConstraints(superview: UIView, view: UIView) {
let topConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .top,
relatedBy: .equal,
toItem: superview,
attribute: .top,
multiplier: 1.0, constant: 0.0)
let bottomConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .bottom,
relatedBy: .equal,
toItem: superview,
attribute: .bottom,
multiplier: 1.0, constant: 0.0)
let leadingConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .leading,
relatedBy: .equal,
toItem: superview,
attribute: .leading,
multiplier: 1.0, constant: 0.0)
let trailingConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .trailing,
relatedBy: .equal,
toItem: superview,
attribute: .trailing,
multiplier: 1.0, constant: 0.0)
superview.addConstraint(topConstraint)
superview.addConstraint(bottomConstraint)
superview.addConstraint(leadingConstraint)
superview.addConstraint(trailingConstraint)
}
static func setSizeConstraints(superview: UIView, view: UIView)
{
let wConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .width,
relatedBy: .equal,
toItem: superview,
attribute: .width,
multiplier: 1.0, constant: 0.0)
let hConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .height,
relatedBy: .equal,
toItem: superview,
attribute: .height,
multiplier: 1.0, constant: 0.0)
superview.addConstraint(wConstraint)
superview.addConstraint(hConstraint)
}
As you can see, I painted my contentView in red color in order to define it on the screen. After showing my PlayerContentView I get this:
PlayerContentView is stretched on the full screen so I'm expecting contentView to be full-size, but clearly it's not. Could someone please refer me to the solution of that issue? Thanks in advance!

Can you set
contentView.translatesAutoresizingMaskIntoConstraints = false

Related

Changing position of UIProgressView?

I added a progress bar to my screen. I want it to be centered horizontally in my container, but I want to move it to the bottom of my screen. How do I edit the third line to change its position?
func addControls() {
progressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Default)
progressView?.center = self.view.center
view.addSubview(progressView!)
}
You could use NSLayoutConstraints and do something like this. The 3rd line being where you put the progress bar on top or beneath the other object.
var otherObject = UIView()
self.view.addSubview(progressView)
let topConstraint = NSLayoutConstraint(item: progressView, attribute: .topMargin, relatedBy: .equal, toItem: otherObject, attribute: .bottomMargin, multiplier: 1, constant: 0)
let leftConstraint = NSLayoutConstraint(item: progressView, attribute: .leftMargin, relatedBy: .equal, toItem: self.view, attribute: .leftMargin, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: progressView, attribute: .rightMargin, relatedBy: .equal, toItem: self.view, attribute: .rightMargin, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: progressView, attribute: .bottomMargin, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
self.view.addConstraints([topConstraint, leftConstraint, rightConstraint, bottomConstraint])
That's really simple.
If you want to add a subview above another one, use this function:
func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView)
To add a subview below another one, use this function:
func insertSubview(_ view: UIView, belowSubview siblingSubview: UIView)

How to put a uiview back in a stackview after I took it out?

I have a viewcontroller that holds multiple stackviews. There is a button that when pressed, the corresponding uiview wil become fullscreen inside the original view. There is a different button that is supposed to make the uiview go back to its original stackview. The uiview itself contains other views. I am having some problems doing that. The uiview does end up in the stackview, but not near the same size/place it used to be. I am not sure how to solve this, and been going at it for several hours now, looking at multiple sources.
This is the code that makes the uiview go fullscreen:
private func moveToFrontOfCardView(v: UIView) {
originalView = v.superview
if let stack = originalView as? UIStackView {
stack.removeArrangedSubview(v)
}
myCardView.addSubview(v)
let topConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: myCardView, attribute: .top, multiplier: 1, constant: 10)
let bottomConstraint = NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: myCardView, attribute: .bottom, multiplier: 1, constant: -10)
let leftConstraint = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: myCardView, attribute: .left, multiplier: 1, constant: 10)
let rightConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: myCardView, attribute: .right, multiplier: 1, constant: -10)
myCardView.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
}
And this is the code I use when I want it to go back:
private func moveToOriginalPosition(v: UIView) {
if let stack = originalView as? UIStackView {
stack.addArrangedSubview(v)
}
}
Does anyone have a clue how I could fix this?
EDIT
I've tried Saqib and Bilals answer, but I get this as a result:
Declare a class variable for tracking view's index
var selectedIndex = 0 // Contains Current Seleceted view's index
overrie func viewDidLoad() { ...
Before removing view from stackview get the view index like this selectedIndex = stack.subviews.index(of: v)
keep reference to all the constraints.
Before adding it back disable all the constraints topConstraint.isActive = false
Now add the view at the same index using stack.insertArrangedSubview(view, at: selectedIndex)
An other option is to create a same new view and just hide/unhide the one in stackview. StackView automatically fills the space accordingly for the hidden views.
You should deActivate the constraints you added to view when removed it from stackView, at the time you want add the view to the stackView again.
For this you should make the constraints instance of your viewController class and next, write your moveToOriginalPosition(v: UIView) method like this:
private func moveToOriginalPosition(v: UIView) {
if let stack = originalView as? UIStackView {
stack.addArrangedSubview(v)
topConstraint.isActive = false
bottomConstraint.isActive = false
leftConstraint.isActive = false
rightConstraint.isActive = false
}
}
Ofcourse, you should remove, these lines of codes from moveToFrontOfCardView(v: UIView) method:
self.topConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: myCardView, attribute: .top, multiplier: 1, constant: 10)
self.bottomConstraint = NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: myCardView, attribute: .bottom, multiplier: 1, constant: -10)
self.leftConstraint = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: myCardView, attribute: .left, multiplier: 1, constant: 10)
self.rightConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: myCardView, attribute: .right, multiplier: 1, constant: -10)
myCardView.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
and add them where you make your view initialized. and replace below lines with above lines in moveToFrontOfCardView(v: UIView) method:
topConstraint.isActive = true
bottomConstraint.isActive = true
leftConstraint.isActive = true
rightConstraint.isActive = true
By the looks of things you don't need to remove the original view. You could make a copy of it then display the copy full screen. Then when you dismiss this copy you release the reference to it

Swift 3 - Create title bar constraints programmatically

I am using Swift 3, iOS 10, XCode 8.2.
In my code, I need to create a UIViewController programmatically and hence, specify its layout and content programmatically as well.
#IBAction func testViewController() {
let detailViewController = UIViewController()
detailViewController.view.backgroundColor = UIColor.white
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
label.text = "Scan Results"
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = UIColor.white
return label
}()
let titleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: titleLabel, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: titleLabel, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: titleLabel, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
]
detailViewController.view.addSubview(titleLabel)
detailViewController.view.addConstraints(titleConstraints)
self.navigationController?.pushViewController(detailViewController, animated: true)
}
In the vertical view (ignore all the other junk; just focus on the blue title bar):
But in the horizontal view:
What is the correct constraint to set so that it takes up the entire width of the bar and there isn't that extra space from the top since the status bar disappears when horizontal?
EDIT
After making #thexande suggestions, I do get an error:
[LayoutConstraints] The view hierarchy is not prepared for the
constraint: <NSLayoutConstraint:0x608000098100
UILabel:0x7fe35b60edc0'Scan Results'.left ==
UIView:0x7fe35b405c20.left (inactive)> 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) _viewHierarchyUnpreparedForConstraint:] to debug. 2017-02-24 21:01:59.807 EOB-Reader[78109:10751346] * Assertion failure in
-[UIView _layoutEngine_didAddLayoutConstraint:roundingAdjustment:mutuallyExclusiveConstraints:],
/BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/NSLayoutConstraint_UIKitAdditions.m:649
2017-02-24 21:01:59.951 EOB-Reader[78109:10751346] * Terminating app
due to uncaught exception 'NSInternalInconsistencyException', reason:
'Impossible to set up layout with view hierarchy unprepared for
constraint.'
I've also updated my code in the original post.
The reason this is happening is because you are using frames. You calculated the frame based on the width of the screen. You do not need frames, you can do this all using auto layout. Instead, you should use constraints to pin your label to it's super view bounds, and give it a static height. for example:
lazy var titleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.titleLabel, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.titleLabel, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.titleLabel, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.titleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
]
then, in viewDidLoad()
self.view.addConstraints(titleConstraints)
You could simplify your label declaration like so. dont forget the auto resizing mask flag to get constraints to work correctly:
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
label.text = "Scan Results"
label.textAlignment = .center
label.textColor = UIColor.white
return label
}()
Finally, you are doing strange math to get the top of your view controller to abut the bottom of your nav bar controller. Remove all that garbage and put the following in viewDidLoad() to get the top of your view controller right against the bottom of your UINavigationBar:
self.edgesForExtendedLayout = []
UPDATES:
The problem here is you are appending views and constraints into a View Controller which has not allocated yet.
The reason we append sub views and constraints within viewDidLoad() is because we cannot add subviews and constraints before the view....did....load into memory. Otherwise, it's not there, and you get the error above. Consider breaking out your detailViewController into a class declaration, like so:
class detailViewController: UIViewController {
let eobTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
label.text = "Scan Results"
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = UIColor.white
return label
}()
lazy var eobTitleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
]
override func viewDidLoad() {
self.view.addSubview(eobTitleLabel)
self.view.addConstraints(self.eobTitleConstraints)
}
}
Also, not to come off as offensive, but your code is kind of a mess. Things you should avoid in the future:
adding constraints to a label which does not exist. ( rename the label of fix the constraints)
you are declaring vars in a outlet method. dont do this, declare methods and properties at the class level.
Read about OOP and how it is implemented in swift. This will help you understand the methods and patterns to complete your task :)

Adding constraints programatically to custom components

I'm developing custom components in Swift by inheriting the default components. Below there is a piece of my code:
class DirectionButton: UIButton {
var backgroundImage: UIImageView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
styleComponent()
}
override init(frame: CGRect) {
super.init(frame: frame)
styleComponent()
}
func styleComponent() {
backgroundImage = UIImageView(image: UIImage(named: "seta-proximo"))
backgroundImage.contentMode = .ScaleAspectFit
self.setNeedsLayout()
self.layoutIfNeeded()
self.addSubview(backgroundImage)
let imageConstraints = NSLayoutConstraint(item: self, attribute: .TrailingMargin, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 10)
backgroundImage.addConstraint(imageConstraints)
}
}
The backgroundImage variable needs to be 10 points from the right part of the button, that's why I need the constraints.
After running I got an exception like that: The view hierarchy is not prepared for the constraint.
How can I add constraints correctly?
Reading the documentation, I followed the tip to use layout anchors as #AjayBeniwal suggested. The code I needed was the following (after I add the subview to the button):
backgroundImage.trailingAnchor.constraintEqualToAnchor(self.layoutMarginsGuide.trailingAnchor, constant: -10).active = true
backgroundImage.bottomAnchor.constraintEqualToAnchor(self.layoutMarginsGuide.bottomAnchor).active = true
backgroundImage.centerYAnchor.constraintEqualToAnchor(self.layoutMarginsGuide.centerYAnchor).active = true
This way, the image is vertically centralized to the button an 20 points far from the right edge.
Edited
first do this :
backgroundImage.translatesAutoresizingMaskIntoConstraints = false
Add constraint to self not backgroundImage
You are adding constraint from self to self (incorrect) add from backroundImage to self
Constraints are incomplete. You are adding only one trailing constraint.
Overall solution is this:
func styleComponent() {
// Change clear.png with your own image name.
backgroundImage = UIImageView(image: UIImage(named: "clear.png"))
backgroundImage.contentMode = .ScaleAspectFit
self.addSubview(backgroundImage)
backgroundImage.translatesAutoresizingMaskIntoConstraints = false
let imageConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: -10)
self.addConstraint(imageConstraints)
let topConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 10)
self.addConstraint(topConstraints)
let bottomConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -10)
self.addConstraint(bottomConstraints)
let leadingConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 10)
self.addConstraint(leadingConstraints)
}
The resultant output is this. You can modifiy the constants values to do according to your own will
In this case, The green is a Button and Red one is background image.
Try this and let me know if you are still getting any warning or complications.
//Give Constrain Outlet to ImageView
func setConstrain()
{
self.imageConstrain.constant = -10
self.topConstraints.constant = 10
self.bottomConstraints.constant = -10
self.leadingConstraints.constant = 10
self.layoutIfNeeded()
}

Swift: programatically copy auto layout constraints from one view to another

I have a UIButton that I set up with auto layout constraints in a storyboard. i also have a UIView that I initiate in the UIViewController's viewDidLoad method. I make this view have (almost) all the same properties as the UIButton but when it I run it in the simulator it doesn't "stick" to the button. here's what I have:
class ViewController: UIViewController {
#IBOutlet weak var someButton: UIButton!
func viewDidLoad() {
super.viewDidLoad()
let someView = UIView()
someView.backgroundColor = UIColor.greenColor()
someView.frame = someButton.bounds
someView.frame.origin = someButton.frame.origin
someView.autoresizingMask = someButton.autoresizingMask
someView.autoresizesSubviews = true
someView.layer.cornerRadius = someButton.layer.cornerRadius
someView.clipsToBounds = true
someView.userInteractionEnabled = false
view.insertSubview(someView, belowSubview: someButton)
}
}
I guess I’m missing the someView.auto layout constraint?
EDIT: i thought accessing the UIButton's constraints would work but they appear to be an empty array. are story board constraints are hidden?
someView.addConstraints(someButton.constraints)
thanks.
Copying the constraints that way fails because:
the constraints in the storyboard are added to the superview and not the button itself
the constraints you try to copy are referencing the button and not your new view
Instead of copying the constraints keep it simple and create new ones and reference the button:
let someView = UIView()
someView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(someView)
view.addConstraints([
NSLayoutConstraint(item: someView, attribute: .Leading, relatedBy: .Equal, toItem: someButton, attribute: .Leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: someView, attribute: .Trailing, relatedBy: .Equal, toItem: someButton, attribute: .Trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: someView, attribute: .Top, relatedBy: .Equal, toItem: someButton, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: someView, attribute: .Bottom, relatedBy: .Equal, toItem: someButton, attribute: .Bottom, multiplier: 1, constant: 0)
])