UITableViewController layout not working - ios

I am trying to position UITableView to the left side of the app, whole height but taking just 1 / 3 of the available width with the code like this:
class ViewController: UIViewController {
var tableController: UITableViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableController = UITableViewController();
addChildViewController(tableController!)
self.view.addSubview(tableController!.view)
tableController!.didMove(toParentViewController: self)
}
override func viewDidLayoutSubviews() {
tableController!.view.backgroundColor = UIColor.red
tableController!.view.frame = CGRect(
x: 0,
y: 0,
width: view.bounds.width / 3,
height: view.bounds.height);
//tableController!.view.frame = view.bounds
}
}
And it looks like this:
And I don't get why the lines are not correctly aligned horizontally and it looks like being cut on the right.
If I give the view controller full width / height by uncommenting the last line, it looks better:

Question 1
Are you properly constraining the TableView in the parent view? You can set proportional constraints. Please see this post on the topic: AutoLayout to keep view sizes proportional
Side Note
For iOS 11, constraining views to layout guides will trigger warnings in your IDE. Consider embedding UI components inside another view that takes up the entire width of the parent view using Auto-resizing Masks. See the topic here: Xcode Auto Layout Resizing Issue

Related

How to make subviews resize to UIScrollView ContentSize?

I have a UIViewController that acts as a Container View Controller. It has a UIScrollView that has the same width as the screen, but it's height is smaller than the height of the screen.
The UIScrollView contains the views of two other UIViewControllers and those views can be horizontally scrolled through.
I set my contentSize like this:
scrollView.contentSize.width = self.view.bounds.width * 2
This works and allows me to scroll through my UIViewController views horizontally.
The following is how I add the UIViewController views to my scrollView:
private func addPanel(viewController: UIViewController, panel: Panel) {
let xOffset: CGFloat!
switch panel {
case .left:
xOffset = 0.0
case .right:
xOffset = self.view.bounds.width
}
let panelView = UIView(frame: CGRect(x: xOffset, y: 0, width: self.view.bounds.width, height: self.scrollView.bounds.height))
scrollView.addSubview(panelView)
let panelViewController: UIViewController! = viewController
var viewBounds = view.bounds
viewBounds.height = panelView.bounds.height
panelViewController.view.frame = view.bounds
panelView.addSubview(panelViewController.view)
addChildViewController(panelViewController)
panelViewController.didMove(toParentViewController: self)
}
For some reason, the UIViewController views don't resize to fit the height of the UIScrollView.
Should I be doing it constraint based? How would I do this. I've been struggling with this for a few days and am at a loss.
Essentially, the UIViewController views will just like like full screen views offset and so you can't see the bottom of the views because the bottom of the screen cuts them off.
I'm just taking a guess without knowing all the code, but one reason could be if you're adding the child view controllers before the scrollview has been layouted.
After you add and set the child views sizes, the scrollview adjusts its size to the phone size but you never update the child views.
My suggestion here would be to add the child view controllers to the scrollview, but move the frame setting code into a layouting method where you know your views have the correct(visible) frames/bounds.
Given you are writing this in a view controller, one method could be -viewDidLayoutSubviews
Hope this makes sense, feel free to ask more questions if it doesn't.

UIStackview fill proportionally in a UITableviewCell takes incorrect frame

Adding proportional fill setting in a UIStackview won't let the view stretch properly. Is this the expected behaviour?
Fill proportionally' distribution type works with intrinsic content size.
So if our vertical stack(height say 600) view has 2 views, ViewA (intrinsic content height 200) and ViewB(intrinsic content height 100), the stack view will size them to ViewA(height 400) and ViewB(height 200).
Here in IB what you see is not what you get.
Dragging to make frames change is useless. Just run the app.
You will see the expected behaviour only when the child views somehow get the intrinsic/constrained height.
How it looks in IB Here the top stack view has views constrained to be of height minimum 10 and 30, i.e. ration 1:4.
What we really get
Top stack view is what we had expected to look like. View with height in ratio 1:4. And bottom one with ratio 1:1, not expected.
You can fix this by creating a custom view
class CustomHeightView: UIView {
var height = 1.0
override public var intrinsicContentSize: CGSize {
return CGSize(width: 0.0, height: height)
}
}
Change the class of your UIView to CustomHeightView
So in the Controller create an outlet for the UIViews
#IBOutlet weak var header_one: CustomHeightView!
#IBOutlet weak var header_two: CustomHeightView!
Then in your viewDidLoad set the proportion the way you want it
override func viewDidLoad() {
super.viewDidLoad()
header_one.height = 1.8
header_two.height = 1.2
}

Why are my UITableViews misplaced on the y-axis when adding them programmatically?

I have as simple setup where I have a view controller embedded in a UINavigationController. On this view controller, I want to display multiple tableViews next to each other (horizontally). I am creating and adding the tableViews in viewDidLoad. It works well except that there is an issue with the y-position of every tableView except for the first one.
When creating the tableViews programmatically, the first tableView is always properly displayed right below the UINavigationBar. However all of the others are displayed "behind" the UINavigationBar even though the y-coordinate is equal to 0 in all of the tableViews.
This is what it looks like when you run it on the simulator (note that the tableView itself has a green backgroundColor):
And this is the code for it:
class ViewController: UIViewController {
var tableViews: [UITableView] = [] // maintained only for debugging purposes
override func viewDidLoad() {
super.viewDidLoad()
let tableViewCount: Int = 5
createTableViews(tableViewCount)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
print(#function, tableViews)
}
func createTableView(count: Int) {
let width = Int(self.view.bounds.width)/count
for i in 0..<count {
let x = width * i
let y = 0
let height = Int(view.bounds.height)
let frame = CGRect(x: x, y: y , width: width, height: height)
let tableView = UITableView(frame: frame, style: UITableViewStyle.Plain)
tableView.backgroundColor = .greenColor()
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
view.addSubview(tableView)
tableView.tag = i
tableViews.append(tableView)
}
}
}
I am only maintaining the tableViews property so that I can print them in viewDidAppear. The console output in viewDidAppear shows that all my tableViews have the same y position on their frames (= 0).
I did find an easy (yet hacky) fix for this. All I have to do is set the first y to 0 and the rest to 64 (which is the height of the status bar plus the height of the navigation bar). Like so:
let y = i == 0 ? 0 : 64
Does anyone have an idea why the tableViews are being misplaced? According to my understanding, all tableViews should be displayed right below the navigation bar, as this is the vertical origin of the view controller's view?
According to my understanding, all tableViews should be displayed right below the navigation bar, as this is the vertical origin of the view controller's view?
This is only the case in my experience if you set myNavigationController.navigationBar.translucent = false. If the navigation bar is translucent, the top of your view is still the top of the screen.
On a side note, you should definitely look into laying these views out via auto layout, rather than this hacky frame math. Would simplify your code a lot.

Discrepancy in the widths of programmatically added views vs. interface builder views

I have come across a conundrum of sorts in regards unexpected (at least for me) sizes of UIViews.
Consider this UIViewController class. interfaceBuilderView was declared in a Storyboard file and constrained to take up the whole area of the UIView. So, I would expect to have interfaceBuilderView be the same size as programicallyCreatedView when calling *.frame.width. But they aren't.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var interfaceBuilderView: MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
let programmicallyCreatedView = MyCustomView(frame: self.view.frame)
//commented out this to get first picture
self.view.addSubview(programmicallyCreatedView)
self.interfaceBuilderView.setAppearance()
self.programmicallyCreatedView.setAppearance()
print(self.view.frame.width)//prints 375
print(self.interfaceBuilderView.frame.width)//prints 600
print(self.programmicallyCreatedView.frame.width)//prints 375
}
}
Now, consider this implementation of the MyCustomView class.
import UIKit
class MyCustomView: UIView {
func setAppearance() {
let testViewWidth: CGFloat = 200.0
let centerXCoor = (self.frame.width - testViewWidth) / 2.0
let testView = UIView(frame: CGRectMake(centerXCoor, 0, testViewWidth, 100))
testView.backgroundColor = UIColor.redColor()
self.addSubview(testView)
}
}
As you can see, I simply draw a red rectagle of width 200.0, and it is supposed to be centered. Here are the results.
Using the Interface Builder created view.
And using the programmatically created view.
As you can see, the programmatically created view achieves the desired results. No doubt because the size printed is the same as the superview (375).
Therefore, my question is simply why is this happening? Furthermore, how can I use a view declared in interface builder and programmatically add other views to it with dimensions and placement that I expect?
A few thoughts:
This code is accessing frame values in viewDidLoad, but the frame values are not yet reliable at that point. The view hasn't been laid out yet. If you're going to mess around with custom frame values, do this in viewDidAppear or viewDidLayoutSubviews.
Nowadays, we really don't generally use frame values anymore. Instead, we define constraints to define the layout programmatically. Unlike custom frame values, you can define constraints when you add the subviews in viewDidLoad.
You have the scene's main view, the MyCustomView and then yet another UIView which is red. That strikes me as unnecessarily confusing.
I would advise that you just add your programmatically created subview in viewDidLoad and specify its constraints. Using the new iOS 9 constraints syntax, you can just specify that it should be centered, adjacent to the top of the view, half the width, and one quarter the height:
override func viewDidLoad() {
super.viewDidLoad()
let redView = UIView()
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(redView)
NSLayoutConstraint.activateConstraints([
redView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor),
redView.topAnchor.constraintEqualToAnchor(view.topAnchor),
redView.widthAnchor.constraintEqualToAnchor(view.widthAnchor, multiplier: 0.5),
redView.heightAnchor.constraintEqualToAnchor(view.heightAnchor, multiplier: 0.25)
])
}
Clearly, adjust these constraints as suits you, but hopefully this illustrates the idea. Don't use frame anymore, but rather use constraints.

UIScrollView not scrolling in Swift

My UIScrollView won't scroll down. I don't know why. I already followed Apple documentation regarding to this issue.
#IBOutlet weak var scroller: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
scroller.scrollEnabled = true
// Do any additional setup after loading the view
scroller.contentSize = CGSizeMake(400, 2300)
}
You need to set the frame of your UIScrollView so that it is less than the contentSize. Otherwise, it won't scroll.
Also, I would recommend that you add the following to your viewDidLoad method:
scroller.contentSize = CGSize(width: 400, height: 2300)
If you are using AutoLayout
Set content size in viewDidAppear which works for me.
override func viewDidAppear(_ animated: Bool) {
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height+300)
}
Alot of the time the code is correct if you have followed a tutorial but what many beginners do not know is that the scrollView is NOT going to scroll normally through the simulator. It is suppose to scroll only when you press down on the mousepad and simultaneously scroll. Many Experienced XCode/Swift/Obj-C users are so use to doing this and so they do not know how it could possibly be overlooked by beginners. Ciao :-)
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
// Do any additional setup after the view
}
override func viewWillLayoutSubviews(){
super.viewWillLayoutSubviews()
scrollView.contentSize = CGSize(width: 375, height: 800)
}
This code will work perfectly fine as long as you do what I said up above
Do not give fix height to scroll view and always give top of first subview to scrollview and bottom of last subview to scrollview. By this way scroll view will automatically grow as per the size of contained subviews. No need to give contentSize to the scrollview.It will work for small as well as large size iPhone.
Swift 3.0 version
scroller.contentSize = CGSize(width: scroller.contentSize.width, height: 2000)
If you are using autolayout, then the contentSize property stops working and it will try to infer the content size from the constraints. If that is the case, then your problem could be that you are not defining the necessary constraints to the content view so that the scrollview can infer the content size.
You should define the constraints of your content view to the top and bottom edges of the scrollview.
If you are using Storyboard:
Put your Content view inside the UIScrollView
Add top, bottom, left and right constraints with the scroll view
Add equal heights and widths constraints
For a vertical scroll set the Equal Heights Constraint priority to 250. For a horizontal scroll set the Equal Widths Constraint priority to 250
In my case, I used UIStackView inside UIScrollView.
Added some views-elements from code to stackview.
It won't scroll.
Fixed it by setting stackview's userInteractionEnabled to false.
The problem could be that your scrollView doesn't know its contentSize like stated above, but the fix is easier than what the above answers are. Like Carlos said but I will elaborate more. If you want your scrollView to scroll vertically(up & down), make your contentView which is in the hierarchy of the scrollView equal width to the ViewController and give it a height constraint that works for your project i.e. 700. For the opposite(horizontally) make the height equal to the ViewController and the width some big number that works for your project.
FWIW, I found that I needed to use sathish's solution from above, though it was insufficient to effect the intervention in viewDidAppear alone. I had to instead make the adjustment for every new content assignment:
func display(pattern: Pattern) {
let text : NSAttributedString = pattern.body()
patternTextView.attributedText = text
// Set the size of the view. Autolayout seems to get in the way of
// a correct size calculation
patternTextView.contentSize = CGSize(width: 348, height: 620)
}
The manifest constants (yeah, I know, yuk, but it makes it easier to understand here) are from the autolayout spec.
It worked for me. In Size Inspector
Layout = Translates Mask into constraints.
Autoresizing = all click.
For Swift 5.6 and iOS 15:
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
let subView: UIView = UILabel()
subView.text = String(repeating: "MMMMMMM ", count: 100)
NSLayoutConstraint.activate([
subView.topAnchor.constraint(equalTo: scrollView.topAnchor),
subView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
subView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
subView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
// Constrain width so the label text wraps and we scroll vertically.
subView.widthAnchor.constraint(lessThanOrEqualTo: scrollView.widthAnchor),
])
Increase the content Height work for me.
I do not know it is a good solution, but you can try to set headerview to empty UITableView.
let scrollView: UIView = UIView(frame: CGRectMake(0, 0, 400, 2300))
tableView.tableHeaderView = scrollView

Resources