Constraints not updating with device orientation change - ios

I have a color slider in a tableView cell that extends from the label to the trailingAnchor of the cell. The issue I am having is when the device orientation changes the color slider does not update its constraints and no longer extends the full length of the cell. Below is my code to set up the constraints on the color slider. Below are images to show the issue I am having. If my phone is already in landscape orientation when I present this scene the color slider extends the full length of the cell as desired. However, if I switch to landscape when already viewing this scene the slider appears as below. Here is the full code if that helps.
func configureColorSlider() {
let colorSlider = ColorSlider()
let xCell = colorCell.contentView.bounds.width
let yCell = colorCell.contentView.bounds.height
colorSlider.frame = CGRect(x: xCell / 4, y: yCell / 4, width: 200, height: 24)
colorSlider.orientation = .horizontal
colorSlider.addTarget(self, action: #selector(ConfigureTimestampTableViewController.changedColor(_:)), for: .valueChanged)
colorCell.contentView.addSubview(colorSlider)
colorSlider.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([colorSlider.leadingAnchor.constraint(equalTo: colorLabel.trailingAnchor, constant: 8),
colorSlider.trailingAnchor.constraint(equalTo: colorCell.contentView.trailingAnchor, constant: -8),
colorSlider.topAnchor.constraint(equalTo: colorCell.contentView.topAnchor, constant: 8),
colorSlider.bottomAnchor.constraint(equalTo: colorCell.contentView.bottomAnchor, constant: -8) ])
}

CALayers will not be resized in response to their parent UIView being resized, whether by auto layout or manually.
You just need to add code to your ColorSlider that updates the layer when the size/position of the ColorSilider changes. Luckily, there is a method on UIView that is specifically for this.
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}

Related

Constraints Issues On UI button

While practicing programmatic constraints using anchors ,I came across a problem that hides the visibility of the button when using an iPhone 8 or 8 plus simulator as shown in the image below. This is due to the current coordinate values I set for the button. However the button becomes visible when I switch to an iPhone 12. How do I set the co-ordinate values in a way that makes the button visible and in same positions on the iPhone 8 or 8 plus ?
let iosImageView: UIImageView = {
let imageview = UIImageView(image: #imageLiteral(resourceName: "icons8-ios-logo-128"))
imageview.translatesAutoresizingMaskIntoConstraints = false
return imageview
}()
let press: UIButton = {
let buttonPress = UIButton.init(type: .roundedRect)
buttonPress.setTitle("Tap Me", for: .normal)
buttonPress.backgroundColor = .black
buttonPress.frame = CGRect(x: 110, y: 700, width: 200, height: 50)
return buttonPress
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
setConstraints()
// Do any additional setup after loading the view.
}
func addSubviews(){
view.addSubview(iosImageView)
view.addSubview(press)
}
func setConstraints(){
NSLayoutConstraint.activate([
iosImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
iosImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
iosImageView.widthAnchor.constraint(equalToConstant: 200),
iosImageView.heightAnchor.constraint(equalToConstant: 200),
press.centerXAnchor.constraint(equalTo: view.centerXAnchor),
press.leadingAnchor.constraint(equalTo: view.leadingAnchor),
press.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
Your constraints for press are invalid. You failed to specify a vertical position. And you forgot to set translatesAutoresizingMaskIntoConstraints to be false. I’m surprised the button ever appears.
(Also cut the line about buttonPress.frame. Frame and constraints are opposites; use one or the other.)

Left-aligning vertical UILabel in Swift

I'm working inside a UICollectionViewCell, trying to get a rotated UILabel to stick to the left of the label at a fixed width. This is what I have been able achieve:
As you can see, the label's dimensions seem relative to the length of the text and disabling auto-resizing has no effect. I would like to constrain the label to ~80 and occupy the full height of the cell, enough for the font with some spacing. The entire code for the UICollectionViewCell:
import Foundation
import UIKit
class DayOfWeekCell: UICollectionViewCell {
let categoryLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
categoryLabel.transform = CGAffineTransform.init(rotationAngle: -CGFloat.pi/2)
categoryLabel.textColor = UIColor.white
categoryLabel.font = UIFont.systemFont(ofSize: 25)
categoryLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(categoryLabel)
categoryLabel.backgroundColor = .blue
categoryLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
categoryLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
categoryLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The ViewController sizeForItem returns the view width with 180 height. It does not play with anything of the cell, only setting the label's text.
I am still relatively new to iOS but have spent the past hour tinkering with this and just cannot get it to play nice! SnapKit is imported but I have had no success with it either. Is there some autosizing going on I'm not aware of? Any help would be greatly appreciated!
I guess there is an issue with the constraints applied using anchors. As all the bottom anchor is also applied, the anchor point is different for different texts. I managed get a decent alignment by applying translating the position again by doing
let transform = CGAffineTransform(rotationAngle: -.pi / 2).translatedBy(x: -25, y: -50)
categoryLabel.transform = transform
Constraints applied to the category label was aligned to leftAnchor, topAnchor and with a width of 100.

Dynamically size UIStackView to the width of the UIViews inside

I am trying to create a UIStackView with three UIViews inside. The UIViews will have a circle with text over / in it.
I would like not to set the StackView to a static number, i would like it to be able to get smaller/grow based on the device the user is using.
Right now, the StackView is being added to the view, and the UIViews are being added to that. The colors are being displayed, but the rounded circles are not and the StackView height is not equal to the leftui's width.
Basically, I need three circles of equal height and width....is there a better way for this?
Here is my code.
#IBOutlet var stack: UIStackView!
override func viewWillLayoutSubviews() {
//let stack = UIStackView()
let leftui = UIView()
let middleui = UIView()
let rightui = UIView()
stack.addArrangedSubview(leftui)
stack.addArrangedSubview(middleui)
stack.addArrangedSubview(rightui)
leftui.backgroundColor = UIColor.red
middleui.backgroundColor = UIColor.blue
rightui.backgroundColor = UIColor.brown
leftui.bounds.size.height = leftui.bounds.width //needs these to new equal
middleui.bounds.size.height = middleui.bounds.width //needs these to new equal
rightui.bounds.size.height = rightui.bounds.width //needs these to new equal
leftui.layer.cornerRadius = leftui.bounds.size.width / 2
middleui.layer.cornerRadius = middleui.bounds.size.width / 2
rightui.layer.cornerRadius = rightui.bounds.size.width / 2
print(leftui.bounds.size.width) //prints 0.0
leftui.clipsToBounds = true
middleui.clipsToBounds = true
rightui.clipsToBounds = true
stack.sizeToFit()
stack.layoutIfNeeded()
view.addSubview(stack)
}
Here is what I was looking for.
This is from the android version of the application.
I think that in order for UIStackView to work its arrangedSubviews have to use autolayout - Check first answer here: Is it necessary to use autolayout to use stackview
This is how you could solve this:
Add a new class for your circular views, these do not do much other than set its layer.cornerRadius to half of their width, so that if height and width are the same they will be circular.
class CircularView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
clipsToBounds = true
layer.cornerRadius = bounds.midX
}
}
You add a widthConstraint with which you will be able to size the elements in the stack view
var widthConstraint: NSLayoutConstraint!
You can then create the UIStackView, I used your code mostly to do this:
override func viewDidLoad() {
super.viewDidLoad()
let leftui = CircularView()
let middleui = CircularView()
let rightui = CircularView()
leftui.translatesAutoresizingMaskIntoConstraints = false
middleui.translatesAutoresizingMaskIntoConstraints = false
rightui.translatesAutoresizingMaskIntoConstraints = false
leftui.backgroundColor = UIColor.red
middleui.backgroundColor = UIColor.blue
rightui.backgroundColor = UIColor.brown
let stack = UIStackView(arrangedSubviews: [leftui, middleui, rightui])
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
widthConstraint = leftui.widthAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([
widthConstraint,
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
leftui.heightAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
middleui.widthAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
middleui.heightAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
rightui.widthAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
rightui.heightAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0)
])
}
Given the constraints set here, circles will have a width/height of 100 and stack view is centred in the view.
Next if you want to do something when view rotates you could implement something like this in your viewController
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: { _ in
if size.width > size.height {
self.widthConstraint.constant = 150
} else {
self.widthConstraint.constant = 100
}
}, completion: nil)
}
It would animate to circles of width/height of 150 in landscape. You can then play with these values to get desired outcome.
To design this, follow the below steps.
create a custom view. in the custom view put all the subviews like
cost title label, price label and the color UIImageView
Now create three object of the custom view with proper data.
Get the device screen width divide by 3 gives each custom view
width, also set the view height as per your requirement and provide
frame for the created custom view
Now add the three views to the StackView.
Hope this will help to design, if you need any more help please comment.

How do I get UIView width and height before adding to subview?

I'm building a UIPageViewController that has a variable number of pages based on the height of views in an array of views.
I have a class called a BlockView that looks like this:
final class BlockView: UIView {
init(viewModel: BlockViewModel) {
super.init(frame: .zero)
let primaryLabel = UILabel()
primaryLabel.text = viewModel.labelText
addSubview(primaryLabel)
constrain(primaryLabel) {
$0.top == $0.superview!.top + 8
$0.bottom == $0.superview!.bottom - 8
$0.left == $0.superview!.left + 8
$0.right == $0.superview!.right - 8
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
What I'd like to be able to do is loop through my array of BlockViews and run print(blockView.frame) and see frames that aren't zero.
Now I know I'm setting my frame to .zero inside of the BlockView.init. That's because I'd like the view to size itself based on its labels.
Is there a function I need to run to achieve this?
Thanks
Try sizeThatFits(_:) to calculate it without putting it to the superview. The only parameter to the method is the CGSize that represents boundaries in which it should be displayed. E.g., if you know the width of the superview (e.g., 340 points) and you want to know how much it will take in height:
let expectedSize = view.sizeThatFits(CGSize(width: 340, height: .greatestFiniteMagnitude))
However, your BlockView does not seem to have a proper constraints set yet. You initialize it with super.init(frame: .zero) - thus it has size 0,0.
And your constraints does not change that, e.g.:
constrain(primaryLabel) {
$0.centerY == $0.superview!.centerY
$0.left == $0.superview!.left + 8
}
This looks like you set the center in Y axis of the label to the center of the block view, and the left anchor of the label to the left anchor of the view. If the blockView would have the size already, that would position the label properly. But right now the size of the block view is not affected by the size of labels at all. I guess you would want to constrain the labels to the left, right, top and bottom anchors of the blockView, so that when you try to calculate the size of the blockView, the autolayout will have to first calculate the size of the labels and based on this the size of the blockView itself.
A possible solution (I am using anchor based autolayout syntax) that you can try to put to initializer of the BlockView:
primaryLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
primaryLabel.topAchor.constraint(equalTo: self.topAnchor, constant: 8).isActive = true
primaryLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -8).isActive = true
primaryLabel.bottomAnchor.constraint(equalTo: secondaryLabel.topAnchor, constant: -8).isActive = true
secondaryLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
secondaryLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -8).isActive = true
secondaryLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -8).isActive = true

How to make auto resizing buttons via swift 3 that work between different Iphones

I'll post down below a part of my code on an app I'm working on for fun, the concept it's different, but the design of the camera session is very similar with the instagram app. I'm also using the PageMenu cocoa pod, that is really good and easy to implement via code.
override func viewDidLoad() {
super.viewDidLoad()
pageMenu?.delegate = self
photoControllerBG.frame = CGRect(x: 0, y : self.view.frame.minY, width: self.view.frame.width, height: self.view.frame.height )
let cameraButton = UIButton(frame: CGRect(x: self.view.frame.midX - , y: photoControllerBG.frame.minY + (self.view.frame.maxY/20*1), width: 130, height: 130))
cameraButton.addTarget(self, action: #selector(cameraButtonPressed(button:)), for: .touchUpInside)
cameraButton.setImage(#imageLiteral(resourceName: "Camera Button.png"), for: UIControlState.normal)
self.view.addSubview(cameraButton)
}
At the moment, the button on an iPhone 7 Plus will display like I want it to, centered in the ViewController and with the Image of the camera button with the same width and height so it stay a perfect circle. The problem presents itself when i switch the simulator with a iPhone SE or iPhone 6, where the button will become too big and not centered. Can you guys help me to find a math formula that it can be used to solve this big but small problem?
Your question implies doing the following in code (swift). Unless you are creating the button in swift I wouldn't do that at all. So, If you have a button created in a storyboard you can scale it appropriately as follows:-
I would use a proportional width constraint combined with an aspect ratio constraint in the story board to achieve this.
It may not be apparent straight away how to do this so I attach a picture journey.
Add your button to the view controller. Image shows final constraints.
Add an Equal Width constraint between the button and it's superview
The button doesn't have equal width to it's superview! My button is 100 px wide and the superview is 375 px wide. So let's calculate that ratio to help us calculate the proportional width.
Edit the Equal width constraint by using the proportional width above as the multiplier.
Add an aspect ratio to the button
Add any remaining constraints (In my case I centred the button horizontally and vertically)
Your constraints for the button should look like this:
let label = UILabel()
label.text = "title"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(#imageLiteral(resourceName: "image"), for: .normal)
view.addSubview(button)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 16)
])
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.topAnchor.constraint(equalTo: label.bottomAnchor),
button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3), //change your multiplier
button.heightAnchor.constraint(equalTo: button.widthAnchor)
])

Resources