UILayoutPriority in an UIScrollView doesn't affect anything - ios

I have a contentview (Defined in an xib file) which has some labels and two buttons in it (white view on screenshot 1.a). If we came how It is shown on the screen, let me draw the scheme;
ViewController View -> View (Custom View Class) -> UIScrollView -> ContentView (Xib file)
Buttons are placed related to bottom margin. They both placed to bottom. Top button has a constraint which has 10 constant related to bottom of last label (This is greater than equal constraint and has UILayoutPriority of 5).
Problem: When I give constraint from button to label even It is greater than equal, button stacks just 10 point below to label. Even there is so much gap in bottom. (Screenshot 2.a)
What I want to achieve: I want that If all contents are fit on the screen with minimum 10 constraint from last label to first button, don't scroll. If not, scroll.
Custom View Class:
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
func initialize() {
self.translatesAutoresizingMaskIntoConstraints = false
let name = String(describing: type(of: self))
let nib = UINib(nibName: name, bundle: .main)
nib.instantiate(withOwner: self, options: nil)
cancelButton.layer.borderColor = UIColor.darkGray.cgColor
cancelButton.layer.borderWidth = 1.0
scrollView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.scrollView)
scrollView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
scrollView.addSubview(contentView)
self.contentView.translatesAutoresizingMaskIntoConstraints = false
//self.contentView.clipsToBounds = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
Example Screenshots:
If content fit into view, I want like below image. If not button can go up till It fits, If It doesn't fit even though, View should be scrollable.
Screenshot 2.a

The following line should be enough to calculate the height of the contents inside the scroll view and give you the proper behavior you expect.
contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor).isActive = true

Related

Subview autolayout constraints in init

I have a custom UIView which is a subclass of UIScrollView. In the init(_ frame) method I add a subview as follows:
contentView = ContentView()
contentView.backgroundColor = UIColor.blue
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
contentView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// create contentView's Width and Height constraints
cvWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0.0)
cvHeightConstraint = contentView.heightAnchor.constraint(equalToConstant: 0.0)
// activate them
cvWidthConstraint.isActive = true
cvHeightConstraint.isActive = true
cvWidthConstraint.constant = timelineWidth //It's non-zero
cvHeightConstraint.constant = 2.0*frame.height
The problem is it takes time for the contentView frame to be updated. It certainly doesn't get updated in init(_ frame) call. When exactly does contentView frame gets set and how do I catch the event of frame updation?
Inside
override func layoutSubviews() {
super.layoutSubviews()
// to do
}
You get parent view actual frame , but keep in mind as it's called multiple times
before setting the constraints, I would make sure the view is added to the parent view, like “self.addSubview(contentView)”. If “self” view is already added to the view hierachy, then the contentView layout will be effective right away. Alternatively, you can override the method viewDidMoveToSuperview, and there you check if the superview is not nil and, if so, you initialize and plug the subview contentView to it

White lines in ViewController on Screen Rotation

I am new to iOS development. I encountered the problem, when I rotate the screen on iPhone X emulator I got white stripes on the side of the screen as you see on the second picture.
I have already set background for ViewController and for TableView.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.backGround
view.tintColor = Colors.backGround
It did not help. When the app starts from landscape mode the issue disappears.
Your blue view area has leading and trailing constraint to safe area.
Just make it to superview.
EDIT:
Here is code for same to achieve with swift code
//Creating tableview
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .lightGray
tableView.dataSource = self
self.view.addSubview(tableView)
//Setting layout of tableview
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
//Setting insets for respect safe area
tableView.contentInset = self.view.safeAreaInsets
Here are screenshots for same.
Vertical
Horizontal
If you created UITableView Programmatically
then follow the below steps
private let tableView = UITableView()
then
func setupView() {
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
}
then
func setupConstraints() {
self.tableView.topAnchor.constraint(equalTo: (self.tableView.superview.safeAreaLayoutGuide.topAnchor), constant: 0).isActive = true
self.tableView.leadingAnchor.constraint(equalTo: (self.tableView.superview.leadingAnchor), constant: 0).isActive = true
self.tableView.trailingAnchor.constraint(equalTo: (self.tableView.superview.trailingAnchor), constant: 0).isActive = true
self.tableView.bottomAnchor.constraint(equalTo: (self.tableView.superview.safeAreaLayoutGuide.bottomAnchor), constant: 0).isActive = true
}
Change your leading and trailing (or left and right) constraints for tableView from safe area to superview and the table will fill the screen horizontally.
Alternatively, just set the background color of the white view to that of the table view. That will ensure your table view content is always visible because it will stay inside the safe area (I'm assuming the white view is self.view here):
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = tableView.backgroundColor
}

Text won't show with constraints added

So here is my current code for the viewdidload and the setup view func
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(bearImageView)
view.addSubview(descriptionText)
view.addSubview(startButton)
setupView()
}
#objc private func start() {
}
private func setupView() {
bearImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
bearImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
bearImageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
bearImageView.heightAnchor.constraint(equalToConstant: 250).isActive = true
descriptionText.topAnchor.constraint(equalTo: bearImageView.bottomAnchor, constant: 10).isActive = true
descriptionText.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
descriptionText.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
startButton.topAnchor.constraint(equalTo: descriptionText.bottomAnchor, constant: 140).isActive = true
startButton.widthAnchor.constraint(equalToConstant: 80).isActive = true
startButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
Both the bearimage and button constraints work fine (minus flipping the phone horizontally but ill fix that later) however the text just refuses to show. The text are made programmatically for istance let descriptionText = UITextView = {...}() and etc. Any of you guys have an idea?
If you look closely you have missed the Height Constraint for your UITextView. If you're using a UILabel or UITextField they don't need a height constraint and can calculate their height based on it's inner contents but UITextView is not going to do that because it will start scrolling if the contents is more than it's height and that's why it can not set the height based on it's inner contents and it's height is zero by default.
add a HeightConstraint to your UITextView as well.
// This will fix your problem
descriptionText.heightAnchor.constraint(equalToConstant: 120).isActive = true
It's possible the image's intrinsic content size is so large that it is expanding such that there is no more space available for the descriptionText label. Try updating the content compression resistance priority of the label to required so it cannot be compressed by the image view.
Swift 3:
descriptionText.setContentCompressionResistancePriority(UILayoutPriorityRequired, for: .vertical)
Swift 4:
descriptionText.setContentCompressionResistancePriority(.required, for: .vertical)

Swift: Programmatically set autolayout constraints for subviews don't resize view

I have the following setup: A UIView adds a bunch of subviews (UILabels) programmatically, and sets also the autolayout constraints, so that the distance between the labels and between the UIViews edges is 10 each. The goal is that the UIView sets its size accordingly to the content of all the subviews (labels with dynamic text) including the spaces.
I use the following code, but it seems not to work. The UIView doesn't resize, it shrinks the labels.
// setup of labelList somewhere else, containing the label data
var lastItemLabel: UILabel? = nil
var i = 1
for item in itemList {
let theLabel = UILabel()
// ... label setup with text, fontsize and color
myView.addSubview(theLabel)
theLabel.translatesAutoresizingMaskIntoConstraints = false
// If it is the second or more
if let lastLabel = lastItemLabel {
theLabel.leadingAnchor.constraint(equalTo: lastLabel.trailingAnchor, constant: 12).isActive = true
theLabel.topAnchor.constraint(equalTo: myView.topAnchor, constant: 10).isActive = true
// if it is the last label
if i == labelList.count {
theLabel.trailingAnchor.constraint(equalTo: myView.trailingAnchor, constant: 12).isActive = true
}
}
// If it is the first label
else {
theLabel.leadingAnchor.constraint(equalTo: myView.leadingAnchor, constant: 12).isActive = true
theLabel.topAnchor.constraint(equalTo: myView.topAnchor, constant: 10).isActive = true
}
lastItemLabel = theLabel
i += 1
}
Since you need your content to be larger than the physical display of the device, you will need to add a UIScrollView to contain your labels.

Adding Stackview to UIScrollView

I have a UIScrollView. It has a stack view. And this stack view contains 12 buttons. (Horizontal scroll view)
Stackview constraints :- top,leading,trailing,bottom to the scroll view and equal widths to the scroll view.
My problem is every time when I run, stack view width limits to the scroll view width and buttons are too small acording to the width of the stack view and my scroll view is not scrollable.
How to make this scrollable
Step-by-Step for setting this up in IB / Storyboards...
Add a view - height 50 leading/top/trailing - blue background
add a scrollview to that view - pin leading/top/trailing/bottom to 0 - set scrollview background to yellow so we can see where it is
add a button to the scroll view
duplicate it so you have 12 buttons
group them into a stack view, and set the stack view's constraints to 0 leading/top/trailing/bottom
and set the stack view's distribution to "equal spacing"
result running in simulator (with no code at all):
and the buttons scroll left and right... no code setting of .contentSize...
So you want this:
Here's how I did it in Xcode 8.3.3.
New Project > iOS > Single View Application.
Open Main.storyboard.
Drag a scroll view into the scene.
Pin top, leading, and trailing of the scroll view to 0. Set height to 30.
Drag a horizontal stack view into the scroll view.
Pin all four edges of the stack view to 0.
Set stack view spacing to 4.
Drag twelve buttons into the stack view.
Set target device to iPhone SE.
Build & run.
Resulting document outline:
If you make your Stackview width equal to the scrollview width, then that's all you'll get, and of course it won't scroll.
Don't give your Stackview a width constraint... let the buttons "fill it out".
Edit: Here is a simple example that you can run directly in a Playground page:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
let stackView : UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.distribution = .equalSpacing
v.spacing = 10.0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scroll view to self.view
self.view.addSubview(scrollView)
// constrain the scroll view to 8-pts on each side
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
// add the stack view to the scroll view
scrollView.addSubview(stackView)
// constrain the stackview view to 8-pts on each side
// this *also* controls the .contentSize of the scrollview
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 8.0).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -8.0).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8.0).isActive = true
// add ten buttons to the stack view
for i in 1...10 {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Button \(i)", for: .normal)
b.backgroundColor = .blue
stackView.addArrangedSubview(b)
}
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc

Resources