Swift 4 Scrollview Constraints Issue - ios

I added a scrollview to my viewController and anchored it to my view, like this:
class MainContainer: UIViewController {
let mainScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .lightGray
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
scrollView.contentInsetAdjustmentBehavior = .never
return scrollView
}()
override func viewDidLoad() {
view.addSubview(mainScrollView)
mainScrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
}
}
The above code works great. Constraints work as expected
I then try to append a view to it by adding
mainScrollView.addSubview(cameraView.view)
That is when the constraints act weird on my scrollview. For some reason the width and height of the scrollview is doubled. Here is a screenshot of my view hierarchy to illustrate my issue
In the image I selected the scrollview and right clicked to "Show Constraints" which for some reason are doubled in width and height. Before adding the view controller the constraints where fine. The added view controller appears fine but the constraints on the scrollview are messed.

Make sure that cameraView.view's top , bottom , leading and trailing constraints are hooked to the scrollview (the superview) then give a height and width to cameraView.view , also don't forget to make translateAutoresizing.... equal to false for the scrollview . . .

Related

Why adding frame doesn't show a UIView when initializing with lazy var in Swift

I'm working on a Swift project and there is one thing I'm not clear about making UIs programmatically.
I tried to display a simple UIView on the screen.
lazy var container: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
view.backgroundColor = UIColor.systemImageGray()
view.layer.cornerRadius = view.layer.bounds.width / 2
view.clipsToBounds = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
setupConstraints()
}
func setupConstraints() {
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -14),
container.widthAnchor.constraint(equalToConstant: 30),
container.heightAnchor.constraint(equalToConstant: 30)
])
}
The code above works fine, but since I set the with and height twice, I feel it's redundant, like UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) and set the width and height constraints in setupConstraints.
Since I set the width and height in UIView's frame, I thought I don't need to set the width and height constraints in the setupConstraints, but it doesn't show the view unless I add the width and height constraints again. So in this case, why I cannot set the width and height in UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) and I also have to add the width/height constraints again?
frame is useful when you are not using the Autolayout engine to place your views.
When you do:
container.translatesAutoresizingMaskIntoConstraints = false
You are explicitly telling the engine to ignore the frame & that you are responsible for applying a new set of constraints.
And hence you eventually do:
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -14),
container.widthAnchor.constraint(equalToConstant: 30),
container.heightAnchor.constraint(equalToConstant: 30)
])
Which sets the positioning & dynamic sizing as per Autolayout's expectations.
translatesAutoresizingMaskIntoConstraints
A Boolean value that determines whether the view’s autoresizing mask
is translated into Auto Layout constraints.
Ref: https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco

iOS: Programatic ScrollView not scrolling (contentSize set)

I have created a scrollView in code and added several labels to it:
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .systemTeal
return scrollView
}()
In viewDidLoad:
view.addSubview(scrollView)
[view1, label1, label2, label3, label4, label5].forEach { scrollView.addSubview($0) }
setViewContraints()
Then in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width: view.width, height: view.height + 300)
}
The first view is anchored to the scrollview safeAreaLayoutGuide topAnchor, then each subsequent label's topAnchor is anchored to the bottom of the previous bottomAnchor. Every view's trailing and leading anchors are anchored to the scrollview's trailing and leading anchors. This is done in setViewConstraints().
view.width and view.height return view.frame.size.width and view.frame.size.height respectively.
All the content appears fine but the scrollView doesn't work. What am I doing wrong?
I realized I needed to set the bottomAnchor of my bottom-most view to the bottom anchor of the scroll view. That did the trick.

layoutMarginsGuide producing unwanted leading and trailing margins

I'm creating constraints programmatically on my views. When I try to use the layoutMarginsGuide anchors, the top and bottom anchors work as expected, but the leading and trailing anchors create margins even if the insets are set at 0. What is creating these unwanted margins and how can I set them correctly?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemRed
view.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
let childView = UIView(frame: .zero)
childView.backgroundColor = .systemIndigo
childView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(childView)
NSLayoutConstraint.activate([
childView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
childView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
childView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
childView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
])
}
So that the side contraints do not contain spaces and are completely glued to the sides, you have to remove the layoutMarginsGuide, it would be like:
NSLayoutConstraint.activate([
childView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
childView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
childView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
childView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
Unlike other views, the system manages the margins of a view controller's root view. By default, it enforces minimum left and right margins of either 16 or 20 points depending on the view width. The top and bottom margins are by default zero.
So if you want less margin of root view then system minimum. you have to make false to viewRespectsSystemMinimumLayoutMargins
viewRespectsSystemMinimumLayoutMargins = false
view.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)

Adding a separator line under custom tableview cell in UITableViewCell

I am trying to add a separator to my uitableview cell. i tried this.
let separator = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 8.0))
cell.contentView.addSubview(separator)
But this adds the separator view on top of the cell, i need it to the bottom.
I also tried this way.
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(view)
view.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor).isActive = true
view.heightAnchor.constraint(equalToConstant: 8.0).isActive = true
view.topAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true
But this gives me no common ancestor error. and i don't want to use storyboard. i need it because i am using same cell at different places, somewhere i need the separator somewhere not. what should i do?
Change constraint for this
view.topAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true
to
view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true
its better to create the line inside init of the custom cell and make it a property
let view = UIView()
then mange its state from cellForRowAt
view.isHidden = true/false
Here is a better way to use separators:
First enable separators in your UITableView by:
myTableView.separatorStyle = .singleLine
Then at your cellForRowAt function:
// Create your cell
// if you want to show the separator then
cell.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
// if you want to hide the separator then
self.separatorInset = UIEdgeInsets(top: 0, left: UIScreen.main.bounds.width, bottom: 0, right: 0)
This would work for cells in the same UITableView as well. Because by adding a left inset of screen width then it won't show on screen and if you set it to 0 it'll be displyed from left edge to right edge of the screen.
Also you can change the color or the insets of the separator by using other properties without using storyboards or xibs.
you can just try below line under viewDidLoad() that include the tableview
override func viewDidLoad() {
tableView.separatorStyle = .singleLine
}

Center Vertically Three UILabels inside UIStackView

I have three UILabels inside an UIStackView. One of them has multiline (last one) and others has only one line. I want them to be centered inside UIStackView so that top and bottom spaces can be dynamic. What am I doing wrong?
headingLabel.translatesAutoresizingMaskIntoConstraints = false
headingLabel.textAlignment = .center
subHeadingLabel.translatesAutoresizingMaskIntoConstraints = false
subHeadingLabel.textAlignment = .center
subHeadingLabel.numberOfLines = 0
bodyLabel.translatesAutoresizingMaskIntoConstraints = false
bodyLabel.textAlignment = .center
bodyLabel.numberOfLines = 0
bodyLabel.text = "My very very long text \n to make it multiline"
textStackView.axis = .vertical
textStackView.distribution = .fillProportionally
textStackView.alignment = .center
textStackView.translatesAutoresizingMaskIntoConstraints = false
textStackView.addArrangedSubview(headingLabel)
textStackView.addArrangedSubview(subHeadingLabel)
textStackView.addArrangedSubview(bodyLabel)
How It is seen now:
How It Should be:
EDIT: I also try to do with fillEqually, but It doesn't change anything. I also set top and bottom anchors of UIStackView. What I want to achieve in here is that for example UIStackView has a height of 100 and all three labels has height of 40. That 60 more space should be equally distributed like 30 - 30 on to and bottom.
Constraints of the UIStackView
myStackView.anchor(contentImageView.bottomAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
One Solution:
I solve it by putting UIStackView inside an UIView and doesn't give height to UIStackView. Just center X and Y anchors. It solve my problem but I'm not sure If It is a proper solution.
Make sure the UIStackView bottom constraint should be greater than equal to zero from UIViewController's view. So that, the UIStackView height will be increased based on the content inside it.
Try this.
let myStackView = UIStackView()
view.addSubview(myStackView)
myStackView.translatesAutoresizingMaskIntoConstraints = false
myStackView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 0.0).isActive = true
myStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0).isActive = true
myStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0).isActive = true
myStackView.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor, constant: 0.0).isActive = true
myStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0).isActive = true
myStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0).isActive = true
What I want to achieve in here is that for example UIStackView has a height of 100 and all three labels has height of 40. That 60 more space should be equally distributed like 30 - 30 on to and bottom.
That is not how a stack view works. If you want three labels with a height of 40 centered then just do that, with no need for a stack view. Or set the stack view height to 40 and center it.

Resources