I would like to setup a UIViewStack so that it will center the two views inside, even though they have different widths. Here is an example:
Is it possible to achieve this type of configuration with UIStackView? I cannot seem to figure it out!
Any help would be appreciated.
You should use nested StackView. Firstly embed View1 and View2 in a Horizontal StackView. Set alignment property center and distribution fill-proportionally. Then embed the Horizontal StackView in a Vertical Stackview. Here I have attached my demo screenshot:
No , you can't . From the apple's Doc
The stack view uses Auto Layout to position and size its arranged views. The stack view aligns the first and last arranged view with its edges along the stack’s axis. In a horizontal stack, this means the first arranged view’s leading edge is pinned to the stack’s leading edge, and the last arranged view’s trailing edge is pinned to the stack’s trailing edge.
You can use Constraints instead.
I can make UIStackView auto grow width after adding views there
Create UIViewController and give UIStackView be center there
Constraint
StackView.leading Priority set to 250 to avoid warning issue in xib
Codes
class StackSampleViewController: UIViewController {
#IBOutlet weak var stackView: UIStackView!
//Keep center auto grow with subviews
#IBAction func touchUpAdd(_ sender: Any) {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
if (stackView.subviews.count % 2) == 0 {
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
view.backgroundColor = .black
} else {
view.widthAnchor.constraint(equalToConstant: 50).isActive = true
view.backgroundColor = .red
}
stackView.addArrangedSubview(view)
}
}
Result
Yes you can, firstly you create main vertical stackview and set aligment center and distribution fill. Then you create second horizontal stackview in main stack and set alignment and distribition fill. Add the last elements you want to add Thats it.
First stackview:
Second stackview:
Related
I have a horizontal stackView inside a vertical stackView. The problem is that i want to register a UITapGesture on the whole row, not just the elements inside. Since the parent(vertical) stackView 's alignment = .leading, it causes the remaining width not occupied of my stackView to become unresponsive to clicks.
In essence, i'd like for the whole row to be clickable and not just the elements inside of it. I've set the horizontal bg color to black to give an idea of the clickable width.
Thanks in advance.
Here is the general idea of my code:
// Parent vertical stackView
private(set) var optionStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .leading
return stackView
}()
// Child horizontal stackView inside optionStackView
private(set) var takePictureStackView: UIStackView = {
let stackView = UIStackView()
stackView.backgroundColor = .black
return stackView
}()
...
takePictureStackView.addArrangedSubview(takePictureImageView)
takePictureStackView.addArrangedSubview(takePictureLabel)
optionStackView.addArrangedSubview(takePictureStackView)
...
// Attaching tapGesture recognizer from VC
takePictureStackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.takePicture)))
Add a constraint for width of takePictureStackView to be equal to the width of optionStackView.
This will expand takePictureStackView horizontally and will increase the tappable area.
How should I setup auto layout constrains so that multiline label stays vertically centered inside scrollview until it's text content becomes too long to be shown at once? When the text length becomes too long text should be aligned at top with the scrollview so the user can see the beginning of text and scroll for more. This is how I tried to setup constrains
for scrollView:
Equal Height to: Superview
Align Trailing to: Safe Area, Equals = -8
Align Leading to: Safe Area, Equals = 8
Align Top to: Safe Area
for label:
Leading Space to: Superview
Equal Width to: Superview
Align Center Y to: Superview
I also added following code to viewDidLoad()
scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
The problem is that but I still get some warnings and also text ends up "vertically centered" inside scrollview without possibility to really scroll to the beginning or the end of it, even when it can not fit whole inside. For scrollview I get warning that it "has ambiguous scrollable content width", while for the label I get warning "trailing constraint is missing, which may cause overlapping with other views"
How about constraining the label height to the height of the scrollView's superview? In that case the label will be always as big as the screen where it is presented, and since by default the text in a UILabel is centered vertically, you would get what you want. See the following Playgrounds example for reference:
import PlaygroundSupport
import UIKit
class A: UIViewController {
let scrollView = UIScrollView()
let label = UILabel()
override func viewDidLoad() {
self.view.backgroundColor = .white
self.view.addSubview(scrollView)
self.scrollView.addSubview(label)
label.numberOfLines = 0
label.text = "How should I setup auto layout constrains so that multiline label stays vertically centered inside scrollview until it's text content becomes too long to be shown at once?"
scrollView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
scrollView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor),
label.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor),
label.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor),
label.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
label.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
label.heightAnchor.constraint(greaterThanOrEqualTo: self.view.heightAnchor),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = A()
I have a view that is composed of an image, a form with 11 UITextfield and a button, but the form is too big for my screen that is why I tried to use a UIScrollview.
The error I have is that my UIScrollview does not work as I can solve this problem.
This is my code:
import UIKit
class LoginCtrl: UIViewController {
let scrollView: UIScrollView = {
let sv = UIScrollView(frame: UIScreen.main.bounds)
sv.isScrollEnabled = true
sv.contentSize = CGSize(width: 2000, height: 5678)
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(r: 0, g: 150, b: 136)
self.view.addSubview(scrollView)
scrollView.addSubview(contenedorCampos)
setear_posicion_scrollView()
setear_posicion_contenedor()
}
func setear_posicion_scrollView(){
//definir x,y,width,height constraints
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
var heightContenedor: NSLayoutConstraint?
func setear_posicion_contenedor(){
//definir x,y,width,height constraints
contenedorCampos.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
contenedorCampos.topAnchor.constraint(equalTo: tabsInicio.bottomAnchor, constant: 12).isActive = true
contenedorCampos.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -24).isActive = true
heightContenedor = contenedorCampos.heightAnchor.constraint(equalToConstant: 400)
heightContenedor?.isActive = true
contenedorCampos.addSubview(txtNombres)
contenedorCampos.addSubview(divider_txtNombres)....
}
}
Thanks
You've defined the relationship between the scrollview and its superview(which defines its frame), but not the relationship between the scrollview and its subviews (which defines its contentSize). As a result, the actual contentSize of the scrollview will be just be (0, 0).
In other words, you never actually laid anything out, at least not in the code you posted.
What you need to do is define layout constraints for the actual child views (everything that is a subview of the scrollview). Make sure to set up constraints definitively pinning these components to the edges of their parent (the scrollview). Once you have defined the constraints sufficiently, the scrollview should have a content size.
Technical note about this
In general, Auto Layout considers the top, left, bottom, and right
edges of a view to be the visible edges. That is, if you pin a view to
the left edge of its superview, you’re really pinning it to the
minimum x-value of the superview’s bounds. Changing the bounds origin
of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of
its bounds. To make this work with Auto Layout, the top, left, bottom,
and right edges within a scroll view now mean the edges of its content
view.
The constraints on the subviews of the scroll view must result in a
size to fill, which is then interpreted as the content size of the
scroll view. (This should not be confused with the
intrinsicContentSize method used for Auto Layout.) To size the scroll
view’s frame with Auto Layout, constraints must either be explicit
regarding the width and height of the scroll view, or the edges of the
scroll view must be tied to views outside of its subtree.
Assume I have a layout as on image below:
Root view - is a viewController's view. I'd like to have my footer text to be
sticked to bottom of screen, if content fits in one screen (main text is small)
have 15pt from main text & 15 pt from bottom of screen in other cases
I understand, that I can calculate main text heigt & compare it to current screen size, but I'd like to find another way (ideally only with constraints).
Is it possible?
To achieve what you are trying to do you need to set the following constraints:
ScrollView:
- top, leading, trailing and bottom equal to RootView's top, leading, trailing and bottom
WrapperView:
- top, leading, trailing and bottom equal to ScrollView's top, leading, trailing and bottom
- width equal to ScrollView's width
- height greaterThanOrEqual to ScrollView's height
TextView:
- top, leading and trailing equal to WrapperView's top, leading and trailing
FooterView:
- leading and trailing equal to WrapperView's leading and trailing
- bottom equal to WrapperView's bottom (with constant 15)
- top greaterThanOrEqual to TextView's bottom (with constant 15)
The key are the two constraints that use the greaterThanOrEqual relation: The WrapperView is at least as high as the ScrollView and the FooterView`s top has at least a distance of 15 from the TextView's bottom.
Here are the constraints when you use a Storyboard:
And this is how you would do it programatically (using SnapKit):
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let scrollView = UIScrollView()
view.addSubview(scrollView)
let wrapperView = UIView()
wrapperView.backgroundColor = .cyan
scrollView.addSubview(wrapperView)
let textView = UILabel()
textView.numberOfLines = 0
textView.font = UIFont.systemFont(ofSize: 30)
textView.text = "You think water moves fast? You should see ice. It moves like it has a mind. Like it knows it killed the world once and got a taste for murder. After the avalanche, it took us a week to climb out. Now, I don't know exactly when we turned on each other, but I know that seven of us survived the slide... and only five made it out. Now we took an oath, that I'm breaking now. We said we'd say it was the snow that killed the other two, but it wasn't. Nature is lethal but it doesn't hold a candle to man."
//textView.text = "You think water moves fast? You should see ice. It moves like it has a mind."
textView.backgroundColor = .yellow
wrapperView.addSubview(textView)
let footer = UILabel()
footer.textAlignment = .center
footer.text = "Footer text"
footer.backgroundColor = .orange
wrapperView.addSubview(footer)
scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(view)
}
wrapperView.snp.makeConstraints { (make) in
make.edges.equalTo(scrollView)
make.width.equalTo(scrollView)
make.height.greaterThanOrEqualTo(scrollView)
}
textView.snp.makeConstraints { (make) in
make.top.left.right.equalTo(0)
}
footer.snp.makeConstraints { (make) in
make.top.greaterThanOrEqualTo(textView.snp.bottom).offset(15)
make.left.right.equalTo(0)
make.bottom.equalTo(-15)
}
}
}
Screenshot with long text:
Screenshot with short text:
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