Adding Stackview to UIScrollView - ios

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

Related

How to add fixed size UIView to UIStackView?

On the latest Xcode (10.1), I'm having trouble adding a fixed size UIView to my UIStackView using programmatic constraints. I think this should be straightforward, but I don't understand where extra constraints are coming from (that UIKit has to break some to layout the views).
The problem is that I am expecting a blue UIView of 100x100. The reality is that the blue UIView is 100% screen width & 100% screen height.
I realize UIStackView uses intrinsicContentSize, but how do I set that correctly using programmatic constraints?
The following is a working playground with an UIStackView & a vanilla UIView added.
Note: if I add the blue UIView directly to the ViewController's view, the size is correct at 100x100 at origin (0,0). Adding it to the stack view causes constraint conflicts.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
// view (100x100)
let fixedSizeView = UIView()
fixedSizeView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeView.backgroundColor = .blue
stackView.addArrangedSubview(fixedSizeView)
NSLayoutConstraint.activate([
fixedSizeView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
The main purpose of a UIStackView is to arrange its subviews.
So, you are constraining your stack view to "fill the screen" and then adding your "blue view" as an arranged subview ... at which point the stack view will "take over" the arrangement of the blue view.
Assuming you are using a stack view because you are planning on adding additional views, you can either allow the subviews to determine the stack view's frame (that is, don't constrain your stack view's width and/or height), or you need to change the stack view's .alignment and/or .distribution properties.
Here is a modification of your playground page to put the 100 x 100 blue view centered in the superview:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
view.addSubview(stackView)
// NSLayoutConstraint.activate([
// stackView.topAnchor.constraint(equalTo: view.topAnchor),
// stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
// ])
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// view (100x100)
let fixedSizeView = UIView()
fixedSizeView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeView.backgroundColor = .blue
stackView.addArrangedSubview(fixedSizeView)
NSLayoutConstraint.activate([
fixedSizeView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
and, here's a modification where two views - blue and red, each at 100 x 100 - get added to a stack view that is constrained to the top of the superview, with .alignment set to .center:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .center
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor),
])
// view (100x100)
let fixedSizeBlueView = UIView()
fixedSizeBlueView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeBlueView.backgroundColor = .blue
stackView.addArrangedSubview(fixedSizeBlueView)
NSLayoutConstraint.activate([
fixedSizeBlueView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeBlueView.heightAnchor.constraint(equalToConstant: 100),
])
// view (100x100)
let fixedSizeRedView = UIView()
fixedSizeRedView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeRedView.backgroundColor = .red
stackView.addArrangedSubview(fixedSizeRedView)
NSLayoutConstraint.activate([
fixedSizeRedView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeRedView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

UILayoutPriority in an UIScrollView doesn't affect anything

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

How to increase UIView height which contains UIStackView

I have a custom view which contains a label, label can have multiple line text. So i have added that label inside a UIStackView, now my StackView height is increasing but the custom view height doesn't increases. I haven't added bottom constraint on my StackView. What should I do so that my CustomView height also increases with the StackView.
let myView = Bundle.main.loadNibNamed("TestView", owner: nil, options: nil)![0] as! TestView
myView.lbl.text = "sdvhjvhsdjkvhsjkdvhsjdvhsdjkvhsdjkvhsdjkvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsdjvhsdjvhsdjvhsdjvhsdjvhsjdvhsdjvhsdjvhsjdvhsdjvhsjdvhsdjvhsdjvhsdjvhsjdv"
myView.lbl.sizeToFit()
myView.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.width, height: myView.frame.size.height)
myView.setNeedsLayout()
myView.layoutIfNeeded()
self.view.addSubview(myView)
I want to increase my custom view height as per my stackview height.
Please help.
Example of stackView constraints with its superview.
Also superview should not have constraints for its height.
You should set the top and bottom anchors of your custom view to be constrained to the top and bottom anchors of your stackview. As your stackView grows, it will push that bottom margin along. Here's a programmatic example:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private lazy var stackView = UIStackView()
private lazy var addLabelButton = UIButton(type: .system)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let stackViewContainer = UIView(frame: view.bounds)
stackViewContainer.backgroundColor = .yellow
stackViewContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackViewContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
addLabelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(addLabelButton)
stackViewContainer.addSubview(stackView)
NSLayoutConstraint.activate([
// Container constrained to three edges of its superview (fourth edge will grow as the stackview grows
stackViewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackViewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackViewContainer.topAnchor.constraint(equalTo: view.topAnchor),
// stackView constraints - stackView is constrained to the
// for corners of its contaier, with margins
{
// Stackview has a height of 0 when no arranged subviews have been added.
let heightConstraint = stackView.heightAnchor.constraint(equalToConstant: 0)
heightConstraint.priority = .defaultLow
return heightConstraint
}(),
stackView.topAnchor.constraint(equalTo: stackViewContainer.topAnchor, constant: 8),
stackView.leadingAnchor.constraint(equalTo: stackViewContainer.leadingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: stackViewContainer.trailingAnchor, constant: -8),
stackView.bottomAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: -8),
// button constraints
addLabelButton.topAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: 8),
addLabelButton.centerXAnchor.constraint(equalTo: stackViewContainer.centerXAnchor)
])
addLabelButton.setTitle("New Label", for: .normal)
addLabelButton.addTarget(self, action: #selector(addLabel(sender:)), for: .touchUpInside)
self.view = view
}
private(set) var labelCount = 0
#objc func addLabel(sender: AnyObject?) {
let label = UILabel()
label.text = "Label #\(labelCount)"
labelCount += 1
stackView.addArrangedSubview(label)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Note that when the UIStackView is empty, its height is not well defined. That is why I set its heightAnchor constraint to 0 with a low priority.
First of all you should add bottom constraint on your UIStackView. This will help auto layout in determining the run time size of UIStackView.
Now create instance of your custom UIView but do not set it's frame and add it to UIStackView. Make sure you Custom UiView has all the constraints set for auto layout to determine it's run time frame.
This will increase height of both UIView and UIStackView based on content of UIView elements.
For more details you can follow my detailed answer on this at https://stackoverflow.com/a/57954517/3339966

How to Make a UIButton fix at UIScrollView?

I am having an UIButton at my UIScrollView and when i zoom my scroll view the button position is not fix...is there any way to make it fix at one location?
Place/set your button over scroll view (not inside scroll view) as shown here in this snapshot. And also set button constraints (position) with respect to super view of your scrollview.
Here is ref. snapshot of hierarchy of position of each view over each-other.
Since iOS 11, UIScrollView has an instance property called frameLayoutGuide. frameLayoutGuide has the following declaration:
var frameLayoutGuide: UILayoutGuide { get }
The layout guide based on the untransformed frame rectangle of the scroll view.
Use this layout guide when you want to create Auto Layout constraints that explicitly involve the frame rectangle of the scroll view itself, as opposed to its content rectangle.
The following Swift 5.1 / iOS 13 UIViewController implementation shows how to use frameLayoutGuide in order to center a UIButton inside a UIScrollView.
import UIKit
class ViewController: UIViewController {
let scrollView = UIScrollView()
let button = UIButton(type: .system)
let imageView = UIImageView(image: UIImage(named: "LargeImage"))
override func viewDidLoad() {
super.viewDidLoad()
// Set scrollView constraints
scrollView.contentInsetAdjustmentBehavior = .never
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// Set imageView constraints
scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])
// Set button constraints (centered)
button.setTitle("Button", for: .normal)
scrollView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: scrollView.frameLayoutGuide.centerXAnchor),
button.centerYAnchor.constraint(equalTo: scrollView.frameLayoutGuide.centerYAnchor)
])
}
}
If you want your button to stay at a fixed size and position when you modify your scrollview, the best way would be to not have it be in the scrollview at all. Add the button to the parent view of the scrollview, then it won't be affected by any changes to the scrollview and can be overlaid on top of it.
You should position your button outside the scroll view and anchor it to the bottom of the page. This way the content in your scroll view will change but your button will remain statically fixed on the view.

UILabel prevents UIButton from being shown until you continue from debug view hierarchy

I have a stack view with 3 arranged subviews; a button with a checkbox image, a label with some text and another button with an 'i' image. When the view is created the last button isn't shown for some reason, so I go to debug view hierarchy, where I can see the button in the list of views but nowhere in the actual view. When I then press continue to stop debugging the view hierarchy, the button all of a sudden shows up and squeezes the label a bit so it fits.
If I remove the label, both buttons are shown just fine. If I put the views in a UIView instead of a stack view the 'i' button is not shown. If I shorten the text on the label, the 'i' button is shown correctly. EDIT: I also tried switching the label and the 'i' button and then both are shown correctly as well.
Do you know what methods are called when you continue from debugging that could change the views? Do you know another way to solve this issue?
Thanks
EDIT: Stackviews constraints are just superviews constrainst with some inset constants:
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 12),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 12),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 10)
])
The buttons and label are just added as arranged subviews with no added constraints
Select the StackView go to Attributes Inspector check if Alignment = fill & Distribution = fill Now select the label inside the StackView go to Size Inspector find content Hugging priority & content Compression Resistance Priority the Horizontal priority to less than 250 So the label can easily be stretched and pressed back
Update:
I added 2 buttons and a label I just used text and colour in the buttons to simulate what you need.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let label = UILabel()
let checkmarkButton = UIButton()
let infoButton = UIButton()
label.text = "Jeg acceptere vilkår og betinging"
label.setContentHuggingPriority(240.0, for: .horizontal)
label.setContentCompressionResistancePriority(240, for: .horizontal)
label.textAlignment = .center
checkmarkButton.setTitle("Check", for: .normal)
checkmarkButton.backgroundColor = UIColor.blue
infoButton.setTitle("info", for: .normal)
infoButton.backgroundColor = UIColor.gray
view.addSubview(label)
view.addSubview(checkmarkButton)
view.addSubview(infoButton)
let stackView = UIStackView(arrangedSubviews: [checkmarkButton, label, infoButton])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
view.addSubview(stackView)
// You have set the trailing constant to 12 which is going outside of the screen if you want to set the trailingAnchor constant programmatically it should be negative
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -12),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12)
])
}

Resources