How to add fixed size UIView to UIStackView? - ios

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()

Related

(Swift 5) UIScrollView scrolls but none of the content scrolls (video included)

I'm trying to learn to build views without storyboard. I tried to build a scrollview. On that scrollview is a UISearchBar, a UIImageView with an image and a UILabel. It works but none of the content moves. The content is all just frozen in place like no matter how far I scroll the search bar will always be on top of the page. and the image on the bottom. I've attached a video to show what I mean. There's also a problem because none of the content is where I want it to be but that's another problem. I realize this is probably because I don't know enough about constraints and autolayout and building views without storyboards.
Here's the video
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label = UILabel()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
// setupLayout()
// tried this here doesn't do anything for me
}
func setupLayout() {
view.addSubview(scrollView)
self.scrollView.addSubview(searchBar)
homeImage.image = UIImage(named: "Treehouse")
self.scrollView.addSubview(homeImage)
label.text = "Inspiration for your next trip..."
self.scrollView.addSubview(label)
// not sure where this label is being added I want it to be underneath the image but it isn't t
let safeG = view.safeAreaLayoutGuide
let viewFrame = view.bounds
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: -10),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
searchBar.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
searchBar.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.9),
searchBar.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 150),
homeImage.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 1.1),
homeImage.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100)
])
// was doing all this in viewDidLayoutSubviews but not sure if this is better place for it
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupLayout()
// tried this in viewDidLoad() and it didn't solve it.
}
}
any help would be appreciated
First, when constraining subviews in a UIScrollView, you should constrain them to the scroll view's Content Layout Guide. You're constraining them to the view's safe area layout guide, so they're never going to go anywhere.
Second, it's difficult to center subviews in a scroll view, because the scroll view can scroll both horizontally and vertically. So it doesn't really have a "center."
You can either put subviews in a stack view, or, quite common, use a UIView as a "content" view to hold the subviews. If you constrain that content view's Width to the scroll view's Frame Layout Guide width, you can then horizontally center the subviews.
Third, it can be very helpful to comment your constraints, so you know exactly what you expect them to do.
Here's a modified version of your posted code:
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
// don't do this
//scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
setupLayout()
}
func setupLayout() {
view.addSubview(scrollView)
//homeImage.image = UIImage(named: "Treehouse")
homeImage.image = UIImage(named: "natureBKG")
label.text = "Inspiration for your next trip..."
// let's use a UIView to hold the "scroll content"
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
// give it a green background so we can see it
contentView.backgroundColor = .green
contentView.addSubview(searchBar)
contentView.addSubview(homeImage)
contentView.addSubview(label)
scrollView.addSubview(contentView)
let safeG = view.safeAreaLayoutGuide
let svContentG = scrollView.contentLayoutGuide
let svFrameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView to all 4 sides of view
// (generally, constrain to safe-area, but this is what you had)
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// constrain contentView to all 4 sides of scroll view's Content Layout Guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: 0.0),
// constrain contentView Width equal to scroll view's Frame Layout Guide Width
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor),
// constrain searchBar Top to contentView Top + 50
searchBar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 50.0),
// constrain searchBar Width to 90% of contentView Width
searchBar.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9),
// constrain searchBar centerX to contentView centerX
searchBar.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Top to searchBar Bottom + 40
homeImage.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 40.0),
// constrain homeImage Width equal to contentView Width
homeImage.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0),
// constrain homeImage centerX to contentView centerX
homeImage.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Height to 1/2 of scroll view frame Height
homeImage.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.5),
// you probably won't get vertical scrolling yet, so increase the vertical space
// between the homeImage and the label by changing the constant
// from 100 to maybe 400
// constrain label Top to homeImage Bottom + 100
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100.0),
// constrain label centerX to contentView centerX
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain label Bottom to contentView Bottom - 20
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),
])
}
}

Extracting a childView and repositioning it inside of a new parentView

I’m trying to rip a view from a stackView that is embedded in a scrollView and then reposition said view in the same location but in another view at the same level in the view hierarchy as the scrollView.
The effect I’m trying to achieve is that I’m animating the removal of a view— where the view would be super imposed in another view, while the scrollView would scroll up and new view would be added to the stackView all while the view that was ripped fades out.
Unfortunately, achieving this effect remains elusive as the rippedView is position at (x: 0, y: 0). When I try force a new frame onto this view its tough because Im guessing the pixel perfect correct frame. Here’s a bit of the code from my viewController:
/*
I tried to make insertionView and imposeView have the same dimensions as the scrollView and
the stackView respectively as I thought if the rippedView’s original superView is the same
dimensions as it’s new superView, the rippedView would be positioned in the same place
without me needing to alter its frame.
*/
let insertionView = UIView(frame: scrollView.frame)
let imposeView = UIView(frame: stackView.frame)
rippedView.removeFromSuperview()
insertionView.addSubview(imposeView)
imposeView.addSubview(rippedView)
let newFrame = CGRect(x: 0, y: 450, width: rippedView.intrinsicContentSize.width, height:
rippedView.intrinsicContentSize.height)
rippedView.frame = newFrame
self.view.addSubview(insertionView)
Before removing rippedView, get it's actual frame:
let newFrame = self.view.convert(rippedView.bounds, from: rippedView)
The issue you are hitting is likely due to the stackView's arranged subviews having .translatesAutoresizingMaskIntoConstraints set to false. I believe this happens automatically when you add a view to a stackView, unless you specify otherwise.
A stackView's arranged subviews have coordinates relative to the stackView itself. So the first view will be at 0,0. Since you are adding a "container" view with the same frame as the stackView, you can use the same coordinate space... but you'll need to enable .translatesAutoresizingMaskIntoConstraints.
Try it like this:
#objc func btnTapped(_ sender: Any?) -> Void {
// get a reference to the 3rd arranged subview in the stack view
let rippedView = stackView.arrangedSubviews[2]
// local var holding the rippedView frame (as set by the stackView)
// get it before moving view from stackView
let r = rippedView.frame
// instantiate views
let insertionView = UIView(frame: scrollView.frame)
let imposeView = UIView(frame: stackView.frame)
// add imposeView to insertionView
insertionView.addSubview(imposeView)
// add insertionView to self.view
self.view.addSubview(insertionView)
// move rippedView from stackView to imposeView
imposeView.addSubview(rippedView)
// just to make it easy to see...
rippedView.backgroundColor = .green
// set to TRUE
rippedView.translatesAutoresizingMaskIntoConstraints = true
// set the frame
rippedView.frame = r
}
Here's a full class example that you can run directly (just assign it to a view controller):
class RipViewViewController: UIViewController {
let aButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .red
v.setTitle("Testing", for: .normal)
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .systemBlue
return v
}()
let stackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.spacing = 8
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(aButton)
view.addSubview(scrollView)
scrollView.addSubview(stackView)
let g = view.safeAreaLayoutGuide
let sg = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
aButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 16.0),
aButton.centerXAnchor.constraint(equalTo: g.centerXAnchor, constant: 0.0),
scrollView.topAnchor.constraint(equalTo: aButton.bottomAnchor, constant: 40.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
stackView.topAnchor.constraint(equalTo: sg.topAnchor, constant: 40.0),
stackView.leadingAnchor.constraint(equalTo: sg.leadingAnchor, constant: 20.0),
stackView.trailingAnchor.constraint(equalTo: sg.trailingAnchor, constant: 20.0),
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -40.0),
stackView.bottomAnchor.constraint(equalTo: sg.bottomAnchor, constant: 20.0),
])
for i in 1...5 {
let l = UILabel()
l.backgroundColor = .cyan
l.textAlignment = .center
l.text = "Label \(i)"
stackView.addArrangedSubview(l)
}
aButton.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
}
#objc func btnTapped(_ sender: Any?) -> Void {
// get a reference to the 3rd arranged subview in the stack view
let rippedView = stackView.arrangedSubviews[2]
// local var holding the rippedView frame (as set by the stackView)
// get it before moving view from stackView
let r = rippedView.frame
// instantiate views
let insertionView = UIView(frame: scrollView.frame)
let imposeView = UIView(frame: stackView.frame)
// add imposeView to insertionView
insertionView.addSubview(imposeView)
// add insertionView to self.view
self.view.addSubview(insertionView)
// move rippedView from stackView to imposeView
imposeView.addSubview(rippedView)
// just to make it easy to see...
rippedView.backgroundColor = .green
// set to TRUE
rippedView.translatesAutoresizingMaskIntoConstraints = true
// set the frame
rippedView.frame = r
}
}

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

Add UILabel over UIImage inside StackView

The part of my storyboard looks like on first photo. On second photo I present constraints. What I am trying to achieve is to put UILabel at the bottom over the UIImageView (with photo "DJI_0049").
Add the label to the same view as the UIStackView.. such that they are siblings/adjacent views. Add the imageView to the stackView and then constrain the label to the imageView.
Since they are siblings it WILL work. Do note that the label CANNOT be an arranged sub-view of the stackView.. otherwise broken constraints will be the result.
Example:
//
// ViewController.swift
// TestSO
//
// Created by Brandon on 2018-02-28.
// Copyright © 2018 XIO. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
let imageView = UIImageView()
let otherView = UIView()
let otherView2 = UIView()
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .fill
self.view.addSubview(stackView)
self.view.addSubview(label)
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(otherView)
stackView.addArrangedSubview(otherView2)
label.text = "Hello World"
label.textColor = UIColor.white
imageView.backgroundColor = UIColor.purple
otherView.backgroundColor = UIColor.red
otherView2.backgroundColor = UIColor.blue
stackView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor),
stackView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor),
stackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
])
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
label.leftAnchor.constraint(greaterThanOrEqualTo: imageView.leftAnchor),
label.rightAnchor.constraint(lessThanOrEqualTo: imageView.rightAnchor),
label.topAnchor.constraint(greaterThanOrEqualTo: imageView.topAnchor),
label.bottomAnchor.constraint(lessThanOrEqualTo: imageView.bottomAnchor)
])
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 100.0),
otherView.heightAnchor.constraint(equalToConstant: 100.0),
otherView2.heightAnchor.constraint(equalToConstant: 100.0)
])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You can’t overlap views like this in a stack view.
Your best option is to use normal auto layout constraints to lay out one of the views (probably the image) and then either use a stack view or constraints to layout the views that you want over it.
With a UIStackView this will not work (for as far I know UIStackView?!). But it can be easily achieved with the following constraints:
Set leading, top and trailing edges from the UIImageView to his superview. Define some height for the UIImageView.
Secondly, set leading and trailing edges from the UILabel to the UIImageView. Make sure the UILabel and UIImageView share the same superview.
Lastly, set the bottom of the UILabel to the bottom of the UIImageView.
i was also able to do this in a stackview by adding the label as a subview to the image view.
cell.avatarImage.leadingAnchor.constraint(equalTo: cell.cellBodyStackView.leadingAnchor, constant: 10).isActive = true
cell.avatarImage.topAnchor.constraint(equalTo: cell.cellBodyStackView.topAnchor).isActive = true
cell.avatarImage.bottomAnchor.constraint(equalTo: cell.cellBodyStackView.bottomAnchor).isActive = true
cell.avatarImage.trailingAnchor.constraint(equalTo: cell.commentLabel.leadingAnchor, constant: -10).isActive = true
cell.avatarImage.widthAnchor.constraint(lessThanOrEqualToConstant: cellWidth * 0.25).isActive = true
cell.avatarImage.addSubview(cell.usernameLabel)
cell.usernameLabel.trailingAnchor.constraint(equalTo: cell.avatarImage.trailingAnchor).isActive = true
cell.usernameLabel.leadingAnchor.constraint(equalTo: cell.avatarImage.leadingAnchor).isActive = true
cell.usernameLabel.bottomAnchor.constraint(equalTo: cell.avatarImage.bottomAnchor).isActive = true
cell.usernameLabel.centerXAnchor.constraint(equalTo: cell.avatarImage.centerXAnchor).isActive = true
cell.usernameLabel.textAlignment = .center
cell.usernameLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5)
cell.usernameLabel.textColor = UIColor.white

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