I have a UIStackView which consist of 5 elements. I want the centered one bigger than others (like in below pic).
How I create UIStackView()
stackView.axis = UILayoutConstraintAxis.horizontal
stackView.distribution = UIStackViewDistribution.fillEqually
stackView.alignment = UIStackViewAlignment.bottom
stackView.spacing = 0.0
stackView.addArrangedSubview(supportedServicesView)
stackView.addArrangedSubview(incidentView)
stackView.addArrangedSubview(contactUsView)
stackView.addArrangedSubview(moreView)
stackView.addArrangedSubview(moreView2)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.anchor(nil, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 90, rightConstant: 0, widthConstant: 0, heightConstant: 0)
How I create my custom UIViews subview positions;
override func updateConstraints() {
logoImage.anchor(self.topAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
label.anchor(self.logoImage.bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 10, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
super.updateConstraints()
}
EDIT:
when I add width anchor to centered view, It gets higher width but because heights are same, It doesn't look like bigger.
contactUsView.widthAnchor.constraint(equalToConstant: self.view.frame.width / 5).isActive = true
EDIT 2: When I give height constraint to any view inside UIStackView, Stackviews position (height only) changes to value which I gave to Views height anchor.
I have just implemented this example in a playground.
The UIStackView uses the intrinsic content size to calculate how to place its arranged subviews in the stack view taking the axis, distribution, spacing etc into consideration.
So if you add both a height and width constraint you should see it working. See the example and screenshot of output below.
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
let stackview = UIStackView(frame: CGRect(x: 0, y: 0, width: 500, height: 150))
stackview.backgroundColor = .white
let colours: [UIColor] = [
.blue,
.green,
.red,
.yellow,
.orange
]
for i in 0...4 {
let view = UIView(frame: CGRect.zero)
view.backgroundColor = colours[i]
view.translatesAutoresizingMaskIntoConstraints = false
if i == 2 {
view.heightAnchor.constraint(equalToConstant: 130).isActive = true
} else {
view.heightAnchor.constraint(equalToConstant: 80).isActive = true
}
view.widthAnchor.constraint(equalToConstant: 75)
stackview.addArrangedSubview(view)
}
stackview.axis = .horizontal
stackview.distribution = .fillEqually
stackview.alignment = .bottom
stackview.spacing = 0.5
PlaygroundPage.current.liveView = stackview
You can drop this code straight into a playground and tweak the settings for spacing, distribution etc to get the desired output.
Related
Here are some code that i am doing to achieve that view
let height = view.bounds.size.height * 2
scrollView = UIScrollView(frame: view.frame)
scrollView.backgroundColor = .red
scrollView.contentSize = CGSize(width: view.frame.width, height: height)
scrollView.contentOffset = CGPoint(x: 0, y: 0)
scrollView.isScrollEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(topView)
scrollView.bringSubviewToFront(topView)
topView.anchor(top: scrollView.topAnchor, leading: scrollView.leadingAnchor, bottom: nil, trailing: scrollView.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: 0), size: CGSize(width: 0, height: 500))
view.addSubview(scrollView)
scrollView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: view.bottomAnchor, trailing: view.trailingAnchor)
top view is UIView anchor() method is the custom method that use NSConstraints to anchor objects
first thing is UIscrollview is also like a UIview which scrolls.so you can have most the properties accessible.
I am trying to create a Scroll View. The view itself is correctly
displayed but it does not scroll, even though I added a view to it and
set the content size.
NOTE: I am not using storyboards but Constraints (AutoLayout) only.
Code:
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.isScrollEnabled = true
sv.backgroundColor = .brown
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
self.view.addSubview(scrollView)
scrollView.anchor(top: self.view.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
scrollView.contentSize = CGSize(width: self.view.frame.width, height: 2000)
print(scrollView.contentSize)
let view = UIView()
view.backgroundColor = .blue
scrollView.addSubview(view)
view.anchor(top: scrollView.topAnchor, left: scrollView.leftAnchor, bottom: scrollView.bottomAnchor, right: scrollView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
.anchor() is a custom extension I made in order to make constraining views easier.
Any help would be appreciated.
It's better not to mix autolayout with frame layout do
1- Comment
scrollView.contentSize = CGSize(width: self.view.frame.width, height: 2000)
2- set equal width constraint between the view inside the scrollview to the vc's view and a height constraint of 2000
For no confuse with vc's view change
let view = UIView()
to
let contentView = UIView()
then add these 2 constarints
contentView.widthAnchor.constraint(equalTo:self.view.widthAnchor).isActive = true
contentView.heightAnchor.constraint(equalToConstant:2000).isActive = true
I have a UIViewController? SingleEventController displaying Events. Because it has dynamic information to show, I chose to create a new class UIScrollView EventScrollView. I tried to realize that based on this very well explained
Answer.
I tried to apply the Pure Auto Layout Approach: Create another UIView contentView in it, which contains all the information and is anchored to all four anchors of the scrollView.
The contentSize of the scrollView is determined in the viewDidLayoutSubviews function in the SingleEventController.
My struggle is, that the rightAnchor seems to be faulty. All the information-Elements go over the border of the view, the word-wrapping for the larger UILabels does not work, and every Item layed out at the right anchor is gone. (For example the usernames in the UITableView nopePeopleTV which is a subview of the contentView.)
This looks like this:
Code in the SingleEventController:
view.addSubview(scrollView)
scrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: buttonViewDividerView.topAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
override func viewDidLayoutSubviews() {
let heightOfAllObjects = scrollView.calculateHeightOfAllObjects()
scrollView.contentSize = CGSize(width: self.view.frame.width, height: heightOfAllObjects + scrollView.heightOfAllPaddings)
}
Code in EventScrollView:
addSubview(contentView)
contentView.addSubview(titleLabel)
contentView.addSubview(locationLabel)
...
contentView.addSubview(nopePeopleTV)
contentView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
titleLabel.anchor(top: contentView.topAnchor, left: contentView.leftAnchor, bottom: nil, right: contentView.rightAnchor, paddingTop: 10, paddingLeft: padding, paddingBottom: 0, paddingRight: padding, width: 0, height: 0)
locationLabel.anchor(top: titleLabel.bottomAnchor, left: contentView.leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: padding, paddingBottom: 0, paddingRight: 0, width: 200, height: 0)
nopePeopleTV.anchor(top: maybePeopleTV.bottomAnchor, left: contentView.leftAnchor, bottom: nil, right: contentView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
layoutIfNeeded()
contentView.layoutIfNeeded()
What am I doing wrong?
My anchor function looks like this:
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
The problem is that you are letting the content view width be dictated from the inside out by the width of the subviews. You need to give the content view a width constraint matching the width of the scroll view itself.
I'll demonstrate the difference with an artificial but complete code-only example:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sv = UIScrollView()
sv.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(sv)
NSLayoutConstraint.activate([
sv.topAnchor.constraint(equalTo: self.view.topAnchor),
sv.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
sv.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
sv.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
])
let cv = UIView() // content view
cv.translatesAutoresizingMaskIntoConstraints = false
sv.addSubview(cv)
NSLayoutConstraint.activate([
sv.topAnchor.constraint(equalTo: cv.topAnchor),
sv.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
sv.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
sv.bottomAnchor.constraint(equalTo: cv.bottomAnchor),
])
let lab = UILabel()
lab.translatesAutoresizingMaskIntoConstraints = false
lab.numberOfLines = 0
lab.text = Array(repeating: "xxx", count: 100).joined(separator: " ")
cv.addSubview(lab)
NSLayoutConstraint.activate([
lab.topAnchor.constraint(equalTo: cv.topAnchor, constant:30),
lab.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
lab.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
])
}
}
Here's the result:
We have a very long label, but it doesn't wrap: it keeps extending to the right, off the screen. That's because the content view itself has its right edge off the screen. That's because we have done nothing to prevent this from happening. The long label is itself making the content view as wide as the label's entire text.
Now I add one final line of code to viewDidLoad:
cv.widthAnchor.constraint(
equalTo:sv.frameLayoutGuide.widthAnchor).isActive = true
The result is this:
The content view is now the width of the scroll view, so the label wraps within the scroll view.
We do not, however, do the same thing to the height of the content view; we want it to grow vertically in accordance with its subviews. And that way, we will be able to scroll vertically (which is what you want) but not horizontally (which is also what you want).
I've looked at almost every stackoverflow solution and for some odd reason my button will not round the corners. Can someone check and see what im doing wrong?
let goToMapsButton = UIButton(type: .custom)
scrollView.addSubview(goToMapsButton)
_ = goToMapsButton.anchor(map.bottomAnchor, left: nil, bottom: seperator.topAnchor, right: self.view.rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 16, rightConstant: 16, widthConstant: 50, heightConstant: 50)
goToMapsButton.backgroundColor = .green
goToMapsButton.layer.cornerRadius = 0.5 * goToMapsButton.bounds.size.width
goToMapsButton.clipsToBounds = true
goToMapsButton.layer.masksToBounds = true
Btw, Im doing this all in the viewDidLoad section of the view controller, if that info makes a difference.
Here is the full viewDidLoadClass for reference:` override func viewDidLoad() {
super.viewDidLoad()
let map = MKMapView()
let view1 = UIView()
view1.backgroundColor = .red
let storeAddress = UILabel()
storeAddress.text = "318 Atwood Avenue"
storeAddress.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
let storeCity = UILabel()
storeCity.text = "Rainy River"
storeCity.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
let seperator = UIView()
seperator.backgroundColor = .lightGray
let goToMapsButton = UIButton(type: .custom)
scrollView.addSubview(map)
scrollView.addSubview(view1)
scrollView.addSubview(storeAddress)
scrollView.addSubview(storeCity)
scrollView.addSubview(goToMapsButton)
scrollView.addSubview(seperator)
map.anchorToTop(scrollView.topAnchor, left: self.view.leftAnchor, bottom: nil, right: self.view.rightAnchor)
map.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.6).isActive = true
_ = storeAddress.anchor(map.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: nil, topConstant: 16, leftConstant: 16, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = storeCity.anchor(storeAddress.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: nil, topConstant: 8, leftConstant: 16, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = goToMapsButton.anchor(map.bottomAnchor, left: nil, bottom: nil, right: self.view.rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 16, rightConstant: 16, widthConstant: 50, heightConstant: 50)
goToMapsButton.backgroundColor = .green
print(goToMapsButton.frame.width)
goToMapsButton.layer.cornerRadius = 0.25 * goToMapsButton.frame.width
goToMapsButton.clipsToBounds = true
goToMapsButton.layer.masksToBounds = true
_ = seperator.anchor(storeCity.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: self.view.rightAnchor, topConstant: 8, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 1)
view1.anchorToTop(map.bottomAnchor, left: self.view.leftAnchor, bottom: scrollView.bottomAnchor, right: self.view.rightAnchor)
view1.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.8).isActive = true
}`
Move this code in viewWillLayoutSubviews:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
goToMapsButton.layer.cornerRadius = 0.25 * goToMapsButton.frame.width
}
Or create your custom class for button with rounded corners:
class RoundedButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
clipsToBounds = true
layer.cornerRadius = cornerRadius
}
}
I face the same issue today, I'm using Anchor too. Well it looks like a one line stuff to me, never think it would takes that much of code.
The difficult is that we cannot get the frame.height or width in UIButton property closure because autolayout has not calculate the size at that time. I do a test to apply the purely round corner in viewDidLayoutSubviews.
And this is what it is looks like, although the declaration is separated for those UIButtons. I really don't like this way, I want to put all button declaration code in the same place, like a closure. However I don't find any other way to do it in internet.
let b1 = UIButton()
let b2 = UIButton()
let b3 = UIButton()
let b4 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
configureLayout()
}
private func configureLayout() {
// disable large title
navigationItem.largeTitleDisplayMode = .never
let topLabel = UILabel()
topLabel.text = "Import your music"
topLabel.font = .systemFont(ofSize: 30, weight: .heavy)
topLabel.numberOfLines = 0
topLabel.textColor = .black
topLabel.textAlignment = .center
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 40
stack.addArrangedSubview(topLabel)
b1.setTitle("Local drive", for: .normal)
b2.setTitle("Google", for: .normal)
b3.setTitle("OneNote", for: .normal)
b4.setTitle("DropBox", for: .normal)
b1.backgroundColor = .myBlue
b2.backgroundColor = .myGreen
b3.backgroundColor = .systemPink
b4.backgroundColor = .myPuple
b1.tag = 1
b2.tag = 2
b3.tag = 3
b4.tag = 4
[b1, b2, b3, b4].forEach { b in
b.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
stack.addArrangedSubview(b)
b.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
stack.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75)
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
[b1, b2, b3, b4].forEach { b in
b.layer.cornerRadius = b1.frame.height / 2
b.layer.cornerCurve = .continuous
}
}
Give your button a frame. like that
goToMapsButton.frame = CGRect(x: xposition, y:yposition, width: widthyouwant, height: heightyouwant)
as currently your size of button is zero 0*0.5 = zero thats why its not applying any radius.
give it frame and it will work...
UIButton might have a background UIImage with rounded corners. It allowes you to set a background UIImage for each UIControlState of your UIButton.
open class UIButton : UIControl, NSCoding {
open func setBackgroundImage(_ image: UIImage?, for state: UIControlState)
}
If your UIButton's size is determined at runtime and the radius is fixed - you can use a resizable image:
open class UIImage : NSObject, NSSecureCoding {
open func resizableImage(withCapInsets capInsets: UIEdgeInsets) -> UIImage
}
On the gif below I use UIImage with size = CGSize(width: 7, height: 7), corner radius = 3 and cap insets = UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)
I have a stackView with 4 items and I want to set width and height of todayLabel in the stackView programmatically. How can I do that? I already tried the code below but it didn't work.
todayLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
todayLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
Thank you very much for your help and this is my code:
let stackView = UIStackView(arrangedSubviews: [todayDate, todayLabel, contentTextView, readMoreLabel])
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.spacing = 5
stackView.anchor(top: viewForImageAndLabel.bottomAnchor, left: safeAreaLayoutGuide.leftAnchor, bottom: shareButton.topAnchor, right: safeAreaLayoutGuide.rightAnchor, paddingTop: 10, paddingLeft: 15, paddingBottom: 10, paddingRight: 15, width: 0, height: 0)
Can anyone help me on this?