Setting 2 label in header programatically - not label seen - swift

I'm trying to set 2 labels, one under other with different font size in the header. The function is called like this:
viewController.navigationItem.titleView = self.setHeader()
And code responsible for generating label is :
private func setHeader(agentName: String = "", isTyping: Bool = false) -> UIView {
let headerLabel: UILabel = {
let label = UILabel()
label.text = self.title
label.font = UIFont.systemFont(ofSize: 21)
label.textColor = UIColor.white
return label
}()
let subheaderLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 10)
return label
}()
let headerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(headerLabel)
view.addConstraints([
NSLayoutConstraint(item: headerLabel, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16),
NSLayoutConstraint(item: headerLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16)
])
return view
}()
if (!agentName.isEmpty) {
if (isTyping) {
subheaderLabel.text = agentName + " is typing ..."
} else {
subheaderLabel.text = agentName
}
headerView.addSubview(subheaderLabel)
}
return headerView
}
When I running IOS app there is nothing shown in the header. What is a reason?

I think you need to set the frame for the headerView. So in the initialization code for the headerView, use initializer with frame:
let headerView: UIView = {
// initialize the view with frame
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 35))
// you want to call this on the headerLabel, not on view
headerLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(headerLabel)
view.addConstraints([
NSLayoutConstraint(item: headerLabel, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16),
NSLayoutConstraint(item: headerLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16)
])
return view
}()
Moreover, I believe headerLabel you want to set translatesAutoresizingMaskIntoConstraints on headerLabel rather than on headerView (headerView is positioned through frame and not Autolayout).
Also, notice that you add subheaderLabel to the view, but you never position it, don't forget about it either (although this should cause only subheaderLabel not to be rendered properly).

Related

UIScrollView with NSLayoutConstraint

UIScrollView is not scrolling the subviews but it does show a scroll-bar.
Here is what I am trying to do
class SetupViewController: UIViewController {
let scrollView = UIScrollView()
let pageLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.setupViews()
self.setupConstraints()
self.setText()
}
func setupViews() {
self.scrollView.backgroundColor = .red
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
// Page Label
self.pageLabel.font = UIFontLocalized(englishFontSize: 22, arabicFontSize: 22)
self.pageLabel.textAlignment = .center
self.pageLabel.translatesAutoresizingMaskIntoConstraints = false
}
func setupConstraints() {
// Add To Sub Views
self.view.addSubview(self.pageLabel)
self.view.addSubview(self.scrollView)
// Page Label
NSLayoutConstraint(item: self.pageLabel, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 30.0).isActive = true
NSLayoutConstraint(item: self.pageLabel, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20.0).isActive = true
NSLayoutConstraint(item: self.pageLabel, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: -40.0).isActive = true
NSLayoutConstraint(item: self.pageLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 36.0).isActive = true
// Scroll View
NSLayoutConstraint(item: self.scrollView, attribute: .top, relatedBy: .equal, toItem: self.line1, attribute: .bottom, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: self.scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: self.scrollView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: self.scrollView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: 1.0, constant: -100.0).isActive = true
self.scrollView.contentSize.height = 2000
// NSLayoutConstraint for rest of elements are removed in this example code.
}
}
I can confirm UIScrollView is added in the view because I checked by giving background red color to scrollview and it does show the background red color in correct position. My issue is the subviews does not move only the scroll bar is moving.
What could be the possible issue here?
Note: NSLayoutConstraint exist for all UIKit elements, I have not added it in the code.
I got this issue sorted our. As pointed out in the comment, the issue was related to superview in constraint which was set to self.view changing it to self.scrollView solved the issue.

Programmatically adding root view to view controller does not position correctly

I am trying to add the root view and subviews to a viewcontroller programmatically, but the view is not filling to screen as expected:
override func loadView() {
self.view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
self.view.backgroundColor = UIColor.blue
}
override func viewDidLoad() {
let alertView = Bundle.main.loadNibNamed("CHRApptTakenAlertView", owner: self, options: nil)![0] as! CHRApptTakenAlertView
self.view.translatesAutoresizingMaskIntoConstraints = false
alertView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(alertView)
self.view.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0))
alertView.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.width, multiplier: 1, constant: 350))
alertView.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.height, multiplier: 1, constant: 250))
alertView.closeBtn.addTarget(self, action: #selector(self.closeBtnTouch), for: UIControlEvents.touchUpInside)
}
Remove the line self.view.translatesAutoresizingMaskIntoConstraints = false, and call self.view.layoutIfNeeded() after the adding the constraints.

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 :)

After adding an NSLayoutConstraint, view disappears

When I add an NSLayoutConstraint to a view, it results in the view disappearing.
I am using the following code:
let test:UIView = UIView(frame: CGRectMake(0, 0, 100, 100))
test.backgroundColor = UIColor.blackColor()
self.view.addSubview(test)
test.translatesAutoresizingMaskIntoConstraints = false
let topCKCtr = NSLayoutConstraint(item: test, attribute: .CenterX, relatedBy: .Equal, toItem: test.superview, attribute: .CenterX, multiplier: 0.5, constant: 0)
topCKCtr.active = true
let topCKCtr1 = NSLayoutConstraint(item: test, attribute: .CenterY, relatedBy: .Equal, toItem: test.superview, attribute: .CenterY, multiplier: 0.5, constant: 0)
topCKCtr1.active = true
self.view.layoutIfNeeded()
self.view.setNeedsDisplay()
When I debug view hierarchy, i see that the view exists, even though it is not visible. See the below screenshot for details - only the constraint is visible, not the view:
There are so many things that needs to be discussed here.
When you use the following
test.translatesAutoresizingMaskIntoConstraints = false
Then it will totally rely on constraints to position and size a view.So you need to set height and width constraints of the view.
let topCKCtr2 = NSLayoutConstraint(item: test, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .Width, multiplier: 1.0, constant: 100)
let topCKCtr3 = NSLayoutConstraint(item: test, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1.0, constant: 100)
Finally This will be the code which you are looking for
let test:UIView = UIView(frame: CGRectMake(0, 0, 100, 100))
test.backgroundColor = UIColor.blackColor()
self.view.addSubview(test)
test.translatesAutoresizingMaskIntoConstraints = false
let topCKCtr = NSLayoutConstraint(item: test, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 0.5, constant: 0)
topCKCtr.active = true
let topCKCtr1 = NSLayoutConstraint(item: test, attribute: .CenterY, relatedBy: .Equal, toItem: self.view, attribute: .CenterY, multiplier: 0.5, constant: 0)
topCKCtr1.active = true
let topCKCtr2 = NSLayoutConstraint(item: test, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .Width, multiplier: 1.0, constant: 100)
topCKCtr2.active = true
let topCKCtr3 = NSLayoutConstraint(item: test, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1.0, constant: 100)
topCKCtr3.active = true

embedding navbar/tabbar breaks constraints of a normally functional view

I have a simple
UIViewController that normally works:
view.backgroundColor
UITextView
UIView (as a spacer between the bottom and the textview)
constraints to pin textview to the view which in turn is pinned to the bottomlayoutguide
tapping textview loads keyboard and the spacer view expands accordingly to avoid the keyboard overlapping my textview
...
//var memoArea = UITextView(frame: CGRectMake(20, 291, 275, 225))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 275.0))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 225.0))
self.view.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 20.0))
// var spacer:UIView = UIView(frame: CGRectMake(84, 518, 160, 6))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 160.0))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 6.0))
self.view.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 84.0))
view.setTranslatesAutoresizingMaskIntoConstraints(false)
spacer.setTranslatesAutoresizingMaskIntoConstraints(false)
memoArea.setTranslatesAutoresizingMaskIntoConstraints(false)
...
...
func updateBottomLayoutConstraintWithNotification(notification: NSNotification) {
let userInfo = notification.userInfo!
let animationDuration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as NSNumber).doubleValue
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let convertedKeyboardEndFrame = view.convertRect(keyboardEndFrame, fromView: view.window)
let rawAnimationCurve = (notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as NSNumber).unsignedIntValue << 16
let animationCurve = UIViewAnimationOptions.init(UInt(rawAnimationCurve))
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
spacerToBottom.constant = CGRectGetMaxY(view.bounds) - CGRectGetMinY(convertedKeyboardEndFrame) - height! - 5
UIView.animateWithDuration(animationDuration, delay: 0.0, options: .BeginFromCurrentState | animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
...
But, after adding either a tab bar or a nav bar to a view that has normally working constraints,
3 things break:
the background no longer renders, yielding a black background
the textview doesn't register taps i.e. keyboard doesn't load
view disregards the bottomlayoutguide. it just shifts my objects up as high as their .Top constraints allow them. the constraints between the textview and uiview are still honored though.
commenting out view.setTranslatesAutoresizingMaskIntoConstraints(false) got rid of all errors.