iOS UITableView tableHeaderView resizes after transition/reappear - ios

I'm getting some odd behavior. I'm setting a tableHeaderView as follows:
class MyTableViewController: UITableViewController {
...
override func viewDidLoad() {
...
var myHeaderView = NSBundle.mainBundle().loadNibNamed("MyHeaderView", owner: self, options: nil).first as? MyHeaderView
tableView.tableHeaderView = myHeaderView
...
}
...
}
When the view loads up the first time, it displays correctly. Auto-layout gives it a height of 30, and the table header height adheres to it.
When I segue to another view (via tapping a cell in the UITableView), then hit the back button, the UITableView draws with the correct height as well.
However, a split second after everything loads correctly the tableViewHeader resizes itself and covers a bit of the first cell. This is extremely frustrating because I can't seem to figure out where it's happening.
I added some log lines. Here's what it looks like after hitting the back button:
viewWillAppear: header frame is Optional((0.0, 0.0, 375.0, 30.0))
viewDidLayoutSubviews: header frame is Optional((0.0, 0.0, 375.0, 30.0))
viewDidAppear: header frame is Optional((0.0, 0.0, 375.0, 30.0))
viewDidLayoutSubviews: header frame is Optional((0.0, 0.0, 375.0, 49.0))
viewDidLayoutSubviews: header frame is Optional((0.0, 0.0, 375.0, 49.0))
From what I can tell, something out of my control changes the height of the tableViewHeader between viewDidAppear and viewDidLayoutSubviews. I can't correct the size in viewDidLayoutSubviews because it triggers an infinite loop.
I'm at a loss as to what to do to fix this. Everything seems/behaves fine until the view reappears. It also breaks the correct height on transition to/from landscape.

Found a workaround that seems to resolve this issue. (Inspired by this post.) The problem seems to be that the combination of auto-resizing masks and autolayout causes some confusion in how the UITableView tries to determine header size. So, a good option is to use autolayout to connect the header view to the parent table view.
First, set TranslatesAutoresizingMaskIntoConstraints to false:
class MyTableViewController: UITableViewController {
...
var _myHeaderView: MyHeaderView!
...
override func viewDidLoad() {
...
_myHeaderView = NSBundle.mainBundle().loadNibNamed("MyHeaderView", owner: self, options: nil).first as! MyHeaderView
myHeaderView.setTranslatesAutoresizingMaskIntoConstraints(false)
tableView.tableHeaderView = myHeaderView
...
}
...
}
Then, override the UITableViewController's updateViewConstraints() method to attach constraints:
override func updateViewConstraints() {
var viewDictionary = ["headerView": _myHeaderView]
_myHeaderView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[headerView(30)]", options: nil, metrics: nil, views: viewDictionary))
tableView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[headerView(==tableView)]", options: nil, metrics: nil, views: ["headerView": _myHeaderView, "tableView": tableView]))
tableView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[headerView]", options: nil, metrics: nil, views: viewDictionary))
tableView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[headerView]", options: nil, metrics: nil, views: viewDictionary))
super.updateViewConstraints()
}
This will respond well to view resizing, etc.

You can Call tableview.reloadData() to solve the problem.

Related

NSLayoutConstraint with VFL is working only horizontally when using '|'

I have the following implementation of a UIView..
struct LoginView {
let loginView: UIView = UIView()
func layoutLoginView() -> UIView {
loginView.translatesAutoresizingMaskIntoConstraints = false
loginView.backgroundColor = UIColor.purple
return loginView
}
}
Then, I subview the above in the viewcontroller as below..
class LoginVC: UIViewController {
private let instanceOfLoginView = LoginView()
override func loadView() {
super.loadView()
view.addSubview(instanceOfLoginView.layoutLoginView())
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[loginView]-|", options: [], metrics: [:], views: ["loginView":instanceOfLoginView.layoutLoginView()]))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[loginView]-|", options: [], metrics: [:], views: ["loginView":instanceOfLoginView.layoutLoginView()]))
}
The problem is that only the 'H' side of the NSLayout is working -check the screenshot below-. The 'V' is not working.
However, when I apply the following "V:|-8-[loginView]-8-|", it works!!!
Could you advise why doesn't the "V:|-[loginView]-|" simply work, please..?
Appreciate your help!
When using VFL, the - character means "use the standard spacing".
In your case:
"H:|-[loginView]-|"
"V:|-[loginView]-|"
you are saying "use the layout margins" which are, by default:
UIEdgeInsets(top: 0.0, left: 16.0, bottom: 0.0, right: 16.0)
Prior to iOS 11 the .layoutMargins of the root view managed by a view controller cannot be changed. To get your purple view to cover the full view, change your VFL to:
"H:|[loginView]|"
"V:|[loginView]|"

Autolayout not working for my nib when added subview

I've created an xib and loaded the nib in my viewDidLayOutSubviews:
I then added the subview:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (myCustomView == nil) {
myCustomView = Bundle.main.loadNibNamed("Help", owner: self, options: nil)?.first as? HelpView
self.view.addSubview(myCustomView!)
}
}
My constraints are all set up correctly in my xib (toggling between devices look okay), however when I launch the app on a different device the autolayout is not updated. How do I fix this? Thank you!
Edit:
Toggled for iPhone 7, but launching for iPhone 7 Plus
Toggled for iPhone 7 Plus, launching for iPhone 7 Plus
Your constraints may be setup correctly in your nib, but you don't have any constraints when you call self.view.addSubview(myCustomView!), so the frame is just going to be whatever it is in the nib file. You need to constraint myCustomView to self.view. Give it equal width, center X, equal top and a fixed height (or use the intrinsic height) and it should be fine. Make sure you turn off translatesAutoresizingMaskIntoConstraints.
just add this line below
self.view.addSubview(myCustomView!)
myCustomView.translatesAutoresizingMaskIntoConstraints = false;
//Views to add constraints to
let views = Dictionary(dictionaryLiteral: ("myCustomView",myCustomView))
//Horizontal constraints
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[myCustomView]|", options: nil, metrics: nil, views: views)
self.view.addConstraints(horizontalConstraints)
//Vertical constraints
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[myCustomView(SpecifyFixedHeight)]|", options: nil, metrics: nil, views: views)
self.view.addConstraints(verticalConstraints)

Swift - constraint issues with UIScrollView (programmatically)

I recently added a scrollview to my viewcontroller. However, this caused my layout to mess up completely.
Here's an image below.
(I gave the UIScrollView a temporary red background, to display, that it's clearly taking the full screen)
now. I have a bunch of things in this view. But to keep it simple I will focus on the top blue bar, which in my app is called "topBar"
First of, I define it in my class.
var topBar = UIView()
I remove the auto sizing, give it a color and add it to my scrollview.
//----------------- topBar ---------------//
topBar.setTranslatesAutoresizingMaskIntoConstraints(false)
topBar.backgroundColor = UIColor.formulaBlueColor()
self.scrollView.addSubview(topBar)
add it to my viewsDictionary:
var viewsDictionary = [ "topBar":topBar]
add the height to my metricsDictionary:
let metricsDictionary = ["topBarHeight":6]
set the height in a sizing constraint.
//sizing constraints
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:[topBar(topBarHeight)]", options: NSLayoutFormatOptions(0), metrics: metricsDictionary, views: viewsDictionary))
And finally the part that doesn't work. I /attempt/ to make it the full width of "scrollView"
// Horizontal Constraints
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[topBar]|", options: nil, metrics: nil, views: viewsDictionary))
and my vertical constraint to put it at the top.
// Vertical Constraints
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topBar]", options: nil, metrics: nil, views: viewsDictionary))
Now as for my scrollview, (the one that's probably causing my layout headaches)
It's set up as follows:
as the very first thing in the class:
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
first thing in my viewDidLoad()
self.view.addSubview(scrollView)
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollView.scrollEnabled = true
and lastly my viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width:2000, height: 5678)
}
^ The width of the contentSize will be changed to the width of the screen (I only want vertical scrolling). But right now that's a minor issue compared to the layout problems I'm having
Any help as to why everything is squeezed together would be greatly appreciated!
I managed to fix it doing the following.
Defining my contentsize in viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width:self.view.bounds.width, height: 5678)
}
and instead of making the view equal to a scrollview, I had to make it a subview of it.
I also had to make a subview of the scrollview, for all my content to work with constraints properly.
self.view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
and all my other objects was made subviews of the "contentView" and not the scrollview.

Programmatically retrieve the width and height of a UIView using Auto Layout and NSLayoutConstraints?

How do you get the width and height of a UIView who's size and position are set using Auto Layout and Apple's Visual Format Language?
Here's the code (view is just the variable from UIViewController):
// Create and add the view
var stageView = UIView()
stageView.setTranslatesAutoresizingMaskIntoConstraints(false) // Since I'm using Auto Layout I turn this off
view.addSubview(stageView)
// Create and add constraints to the containing view
let viewsDictionary = ["stageView":stageView]
let horizontalConstraints: NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-150-[stageView]-150-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary)
let verticalConstraints: NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-100-[stageView]-150-|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: viewsDictionary)
view.addConstraints(horizontalConstraints)
view.addConstraints(verticalConstraints)
println("stageView.frame=\(stageView.frame)")
and got:
stageView.frame=(0.0,0.0,0.0,0.0)
so I tried:
let fittingSize = stageView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
println("fittingSize=\(fittingSize)")
and got:
fittingSize=(0.0,0.0)
I can't seem to find a way to get the size. I'm able to add subviews to stageView that place just fine using Auto Layout and Visual Format Language, but I can't get width and height for stageView which I need to further position those subviews.
Any ideas?
You have a few options:
You can force the layout engine to size the views immediately by calling setNeedsLayout and then call layoutIfNeeded. This is not recommended because it's inefficient and any manual frames required for layout might not have been set yet. You can read more about this approach on my answer to this question.
You can also wait until the subviews have been updated in the view controller:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
println("stageView.frame = \(stageView.frame)")
}
If you want to know within a UIView subclass (or more often, a UITableViewCell subclass, you can check after layoutSubviews has run:
override func layoutSubviews() {
super.layoutSubviews()
println("self.frame = \(self.frame)")
}
You need to check the frame inside viewDidLayoutSubviews.
This function run after constraint calculation
Its suppose to look something like this
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
//Print frame here
}

A view height is 568 on 3.5-inch screen

I need to create a view with a scroll view and a page control in it, and place 7 views inside scroll view.
To lay out subviews inside the scroll view I use pure Auto layout Approach, that is described here.
So I have my controller with XIB file (I don't use storyboards here) that is pretty simple: it's a UIScrollView and UIPageControl with all constraints set up.
And I have a XIB for a UIView subclass Slide which has 2 UIImageViews and 1 UILabel, and there's also some constraints.
To add some views to UIScrollView I use this code in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
var pSlide: Slide?
for var i = 0; i < 7; i++ {
var slide = Slide(frame: self.view.bounds, imageName: "slide-\(i+1)-bg", text: NSLocalizedString("slides_\(i+1)", comment: ""))
slide.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollView.addSubview(slide)
var dict: [NSObject : AnyObject] = ["currentSlide" : slide]
if let previousSlide = pSlide {
dict["previousSlide"] = previousSlide
let constraintsHorizontal = NSLayoutConstraint.constraintsWithVisualFormat("H:[previousSlide][currentSlide]", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsHorizontal)
let constraintsVertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[currentSlide]|", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsVertical)
} else {
let constraintsVertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[currentSlide]|", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsVertical)
let constraintsLeft = NSLayoutConstraint.constraintsWithVisualFormat("H:|[currentSlide]", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsLeft)
}
if i == 6 {
let constraintsRight = NSLayoutConstraint.constraintsWithVisualFormat("H:[currentSlide]|", options: nil, metrics: nil, views: dict)
scrollView.addConstraints(constraintsRight)
}
pSlide = slide
}
pageControl.numberOfPages = numberOfSlides
view.layoutSubviews()
}
In this piece of code I create a Slide instance, and set all necessary constraints to it, according to pure Auto Layout approach.
init() method of the Slide class looks like this:
init(frame: CGRect, imageName: String, text: String) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("Slide", owner: self, options: nil)
self.addSubview(self.view)
self.view.frame = frame
self.layoutIfNeeded()
println("Frame is \(frame); view.frame is \(self.view.frame)")
backgroundImage.image = UIImage(named: imageName)
textLabel.text = text
}
I hoped that
self.view.frame = frame
self.layoutIfNeeded()
will help me but no. The problem is, on 3.5 inch screen all my UIScrollView subviews have the height of 568, which is the normal height for 4 inch display, but not for 3.5 inch.
I'm checking the height in viewDidAppear(animated:) method. But, in init() method of Slide class the height appears to be ok — 480.
I'm trying to solve it for second day already, and still nothing works. I know that this may be much more simple to implement without using Auto Layout and Interface Builder, but I need to do it with these.
I used UIPageViewController instead of all this mess, and it works just fine.

Resources