I've been having this issue with HoshiTextField for quite some time now, I also opened an issue on git but didn't get an answer...
This is how it should look like and how it looks like if the user is selecting the textField:
The problem occurs when setting the textField to becomeFirstResponder inside viewDidLoad or when popping a ViewController while the textField inside the first VC was selected. Apparently that messes up the frames or constraints of the textField but I have absolutely no idea how to fix this.
As you can see the "Email-Adesse"-text is moving to the upper left and when pushing and poping back to the ViewController it moves even further outside the constraints. When checking the View Hirarchy with the debugger the "Email-Adresse"-Text looks perfectly in place even though it isn't. Setting up the constraints inside viewDidAppear didn't change anything.
I constrain the textFields like every other element:
let emailTextField: HoshiTextField = {
let v = HoshiTextField()
v.borderActiveColor = .white
v.borderInactiveColor = .white
v.textColor = .white
v.font = UIFont(name: "AvenirNext-Regular", size: 17)
v.placeholder = "Email-Adresse"
v.placeholderColor = .white
v.placeholderFontScale = 0.8
v.minimumFontSize = 13
v.borderStyle = .line
v.autocapitalizationType = .none
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
emailTextField.topAnchor.constraint(equalTo: theLabel.bottomAnchor, constant: 20).isActive = true
emailTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
emailTextField.heightAnchor.constraint(equalToConstant: 60).isActive = true
If anyone can help me out here I would be so grateful! I hope the problem is clear, you can also look at my project to see the problem yourself:
Git repo to my project
Here is full test controller with 2 alternates of possible solution
Demo: Alternate 1 - often appears already expanded
Demo: Alternate 2 - always there is a delay, expanding is visible
class ViewController2: UIViewController {
#IBOutlet weak var theLabel: UILabel!
private weak var emailTextField: HoshiTextField!
override func viewDidLoad() {
super.viewDidLoad()
emailTextField = {
let v = HoshiTextField()
v.borderActiveColor = .white
v.borderInactiveColor = .white
v.textColor = .white
v.font = UIFont(name: "AvenirNext-Regular", size: 17)
v.placeholder = "Email-Adresse"
v.placeholderColor = .white
v.placeholderFontScale = 0.8
v.minimumFontSize = 13
v.borderStyle = .line
v.autocapitalizationType = .none
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
self.view.addSubview(emailTextField)
emailTextField.topAnchor.constraint(equalTo: theLabel.bottomAnchor, constant: 20).isActive = true
emailTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
emailTextField.heightAnchor.constraint(equalToConstant: 60).isActive = true
// DispatchQueue.main.async {
// self.emailTextField.becomeFirstResponder() // Alternate 1
// }
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
emailTextField.becomeFirstResponder() // Alternate 2
}
}
Sorry I misunderstood your question at first.
I feel like the issue is that at some point as the HoshiTextField is selected and leaves the screen, it becomes deselected, but it does not update it's layout. So then when you come back the animations still stay relatively the same, but from a different angle.
I'm not sure how to solve that problem precisely, but I was able to get the screen to look normal after adding the following to your EmailVC
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
}
I hope that that's fine. It's not really solving the issue, but it does get the UI of your app looking proper in this scenario.
Related
I have one textField.
private var verseTitle: UITextField = {
let tf = UITextField()
tf.placeholder = "TITLE"
tf.font = UIFont(suite16: .tBlackItalic, size: 18)
tf.textColor = .black.withAlphaComponent(0.5)
tf.returnKeyType = .done
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
In viewDidLoad method, I have assigned self as delegate.
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
verseTitle.delegate = self
}
In viewDidLayout method, I'm using stack view to add textField to the view.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//Title
let titleStack = UIStackView()
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .equalSpacing
titleStack.spacing = 8
titleStack.addArrangedSubview(verseTitle)
titleStack.addArrangedSubview(floorView)
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)
NSLayoutConstraint.activate([
floorView.heightAnchor.constraint(equalToConstant: 13),
floorView.widthAnchor.constraint(equalToConstant: 13),
titleStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
Now, the problem I'm facing is, that when I start typing in the textField, the keyboard gets dismissed only after I type one letter. I'm not sure why this is happening. I have to tap on the field after entering each letter. For some reason, the focus is taken away from the field after each letter is entered (unless I tap on a suggested autocorrect - the whole string is correctly added to the string at once)
What's going on here is your view, including the UITextField gets re-created after each keystroke because your view construction is in: viewDidLayoutSubviews(), When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method.
To fix it, move the code to ViewDidLoad, so the view is created only once:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
verseTitle.delegate = self
let titleStack = UIStackView()
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .equalSpacing
titleStack.spacing = 8
titleStack.addArrangedSubview(verseTitle)
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)
NSLayoutConstraint.activate([
titleStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
To design and create my UI, I always use auto layouts and do it programmatically instead of using storyboard.
In every view class of mine, I have a method called
private func setupView(frame:CGRect) {
/* START CONTAINER VIEW */
containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: frame.width * (13 / IPHONE8_SCREEN_WIDTH)).isActive = true
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -frame.width * (13 / IPHONE8_SCREEN_WIDTH)).isActive = true
containerView.topAnchor.constraint(equalTo: topAnchor, constant: frame.height * (26 / IPHONE8_SCREEN_HEIGHT)).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
containerView.backgroundColor = UIColor.white
/* END CONTAINER VIEW */
...
}
to initialize the components. Now let's say, in the method above, I initialize 10 UI components which are properly displayed when I run my code. However, depending on some variables, I have another function that is being called
private func addNextRoundInformation() {
..
nextRoundLabel = UILabel()
nextRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabel)
nextRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
nextRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
nextRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
}
which should place a new label between some others which were already initialized.
Of course, when putting the new label between some particular ones, I also update the auto layout constraints of the of the bottom label like
private func updateNumberOfWinnersLabelConstraint() {
numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: nextRoundLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: nextRoundLabelValue.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
}
The topAnchor of each label depends on the bottom anchor of the previous one.
With this approach, I can't see nextRoundLabel at all. It only appears, if I initialize it in the private func setupView(frame:CGRect) {}
Why?
You could do this with "Top-to-Bottom" constraints, but it would be rather complex.
You would need to essentially create a "Linked List" to track each view, the views above and below it, and its constraints.
So, to "insert" a new view after the 3rd view, you would need to:
deactivate the inter-view constraints
insert the new view into the linked list
re-create and activate the new constraints
Putting your views in a UIStackView turns that process into a single line of code:
stackView.insertArrangedSubview(newView, at: 3)
Here's a quick example:
class ViewController: UIViewController {
let testView: SampleView = SampleView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let infoLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.numberOfLines = 0
v.text = "Tap to Insert \"New Label\"\nafter \"Label 3\""
return v
}()
let btn: UIButton = {
let v = UIButton()
v.setTitle("Insert", for: [])
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
v.backgroundColor = .systemBlue
return v
}()
[infoLabel, btn, testView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
infoLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75),
infoLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
btn.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
btn.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75),
btn.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 40.0),
testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
testView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
])
btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
}
#objc func btnTapped(_ sender: Any?) {
testView.insertNew()
}
}
class SampleView: UIView {
var containerView: UIView!
var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .red
setupView(frame: .zero)
}
private func setupView(frame:CGRect) {
/* START CONTAINER VIEW */
containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(containerView)
stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(stackView)
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 12),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0),
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8.0),
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8.0),
])
containerView.backgroundColor = UIColor.white
/* END CONTAINER VIEW */
// add 10 labels to the stack view
for i in 1...10 {
let v = UILabel()
v.text = "Label \(i)"
v.backgroundColor = .green
stackView.addArrangedSubview(v)
}
}
func insertNew() {
let v = UILabel()
v.text = "New Label"
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
// we're adding a label after 3rd label
stackView.insertArrangedSubview(v, at: 3)
}
}
It starts looking like this:
after tapping the "Insert" button, it looks like this:
Edit
To explain why your current approach isn't working...
Starting with this layout:
each label's Top is constrained to the previous label's Bottom (with constant: spacing).
Those constraints are indicated by the blue arrows.
You then want to "insert" Next Round Label between Round Ends In and Winners Per Round:
Your code:
adds the label
adds a constraint from the Top of Next Round Label to the Bottom of Round Ends In
adds a constraint from the Top of Winners Per Round to the Bottom of Next Round Label
but... Winners Per Round already has a .topAnchor connected to Round Ends In, so it now has two top anchors.
The conflicting constraints are shown in red:
As I said, I think your description of what you're trying to do would lend itself to using stack views, and would make "inserting" views so much easier.
But, if you need to stick with your current "Top-to-Bottom" constraints approach, you have several options.
One - remove all the labels, then re-add and re-constrain them, including the one you're inserting.
Two - track the constraints (using an array or custom object properties) so you can deactivate the conflicting constraint(s).
Three - use some code along the lines of
let theConstraint = containerView.constraints.first(where: {($0.secondItem as? UILabel) == roundEndsInLabel})
to "find" the constraint that needs to be deactivated.
I found a working solution:
First, I declared two helper variables
/* START HELPER VARIABLES */
var numberOfWinnersLabelTopConstraint:NSLayoutConstraint?
var numberOfWinnersLabelValueTopConstraint:NSLayoutConstraint?
/* END HELPER VARIABLES */
then I made some minor adjustments in my setupView function:
private func setupView(frame:CGRect) {
...
/* START NUMBER OF WINNERS PER ROUND LABEL */
numberOfWinnersPerRoundLabel = UILabel()
numberOfWinnersPerRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(numberOfWinnersPerRoundLabel)
numberOfWinnersPerRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
numberOfWinnersPerRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
// numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true // Replacing with helper variable
numberOfWinnersLabelTopConstraint = numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT))
numberOfWinnersLabelTopConstraint?.isActive = true
numberOfWinnersPerRoundLabel.text = NSLocalizedString(NUMBER_OF_WINNERS_PER_ROUND_TEXT, comment: "")
numberOfWinnersPerRoundLabel.textColor = .darkGray
numberOfWinnersPerRoundLabel.adjustsFontSizeToFitWidth = true
numberOfWinnersPerRoundLabel.font = .systemFont(ofSize: 12)
/* END NUMBER OF WINNERS PER ROUND LABEL */
/* START NUMBER OF WINNERS PER ROUND LABEL VALUE */
numberOfWinnersPerRoundLabelValue = UILabel()
numberOfWinnersPerRoundLabelValue.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(numberOfWinnersPerRoundLabelValue)
numberOfWinnersPerRoundLabelValue.leadingAnchor.constraint(equalTo: currentRoundLabelValue.leadingAnchor).isActive = true
numberOfWinnersPerRoundLabelValue.widthAnchor.constraint(equalTo:currentRoundLabelValue.widthAnchor).isActive = true
// numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: numberOfWinnersPerRoundLabel.topAnchor).isActive = true // replacing with helper variable
numberOfWinnersLabelValueTopConstraint = numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: numberOfWinnersPerRoundLabel.topAnchor)
numberOfWinnersLabelValueTopConstraint?.isActive = true
numberOfWinnersPerRoundLabelValue.textColor = .black
/* END NUMBER OF WINNERS PER ROUND LABEL VALUE */
By introducing the helper variables, I could easily deactivate the topConstraint when adding the nextRoundLabel
private func addNextRoundInformation() {
nextRoundLabel = UILabel()
nextRoundLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabel)
nextRoundLabel.leadingAnchor.constraint(equalTo: currentRoundLabel.leadingAnchor).isActive = true
nextRoundLabel.widthAnchor.constraint(equalTo:currentRoundLabel.widthAnchor).isActive = true
nextRoundLabel.topAnchor.constraint(equalTo: roundEndsInLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT)).isActive = true
nextRoundLabel.text = "Next round starts in"
nextRoundLabel.textColor = .darkGray
nextRoundLabel.font = .systemFont(ofSize: 12)
nextRoundLabelValue = UILabel()
nextRoundLabelValue.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(nextRoundLabelValue)
nextRoundLabelValue.leadingAnchor.constraint(equalTo: currentRoundLabelValue.leadingAnchor).isActive = true
nextRoundLabelValue.widthAnchor.constraint(equalTo:currentRoundLabelValue.widthAnchor).isActive = true
nextRoundLabelValue.topAnchor.constraint(equalTo:nextRoundLabel.topAnchor).isActive = true
nextRoundLabelValue.textColor = .black
nextRoundLabelValue.text = "Next round label value"
nextRoundLabelValue.font = .systemFont(ofSize: 14)
}
private func updateNumberOfWinnersLabelConstraint() {
numberOfWinnersLabelTopConstraint?.isActive = false // Deactivate previous constraint
numberOfWinnersLabelValueTopConstraint?.isActive = false
numberOfWinnersLabelTopConstraint = numberOfWinnersPerRoundLabel.topAnchor.constraint(equalTo: nextRoundLabel.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT))
numberOfWinnersLabelTopConstraint?.isActive = true
numberOfWinnersLabelValueTopConstraint = numberOfWinnersPerRoundLabelValue.topAnchor.constraint(equalTo: nextRoundLabelValue.bottomAnchor, constant: frame.height * (19 / IPHONE8_SCREEN_HEIGHT))
numberOfWinnersLabelValueTopConstraint?.isActive = true
}
Basically, I only had to update the topConstraints of the numberOfWinnersPerRoundLabel and numberOfWinnersPerRoundLabelValue
since everything else would be the same. No changes needed for currentRoundLabel.
I tested it and it worked!
I have written a function that defines and adds a UILabel to the UIView - however the constraints are not being executed as the label remains in the top left corner of the screen.
Heres the code:
func timeAdded(screenHeight: CGFloat, screenWidth: CGFloat, viewController: UIView) {
let readyLabel = UILabel()
readyLabel.backgroundColor = UIColor.white
readyLabel.textAlignment = .center
readyLabel.text = "00:00"
readyLabel.textColor = UIColor.blue
//readyLabel.alpha = 0
readyLabel.font = UIFont(name: "panl-font-4", size: 60)
readyLabel.sizeToFit()
viewController.addSubview(readyLabel)
readyLabel.centerXAnchor.constraint(equalTo: viewController.centerXAnchor, constant: 0).isActive = true
readyLabel.centerYAnchor.constraint(equalTo: viewController.centerYAnchor, constant: 0).isActive = true
}
Any advice appreciated
You may need
readyLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
readyLabel.centerXAnchor.constraint(equalTo: viewController.centerXAnchor)
readyLabel.centerYAnchor.constraint(equalTo: viewController.centerYAnchor)
])
So it's better to read This
I have my UI structured say Level 1(UP), Level 2(DOWN) with some controls
In level 1, I have a label L1
In level 2, I have a button and label L2
In level 2 my button may be removed in runtime and I wanted my label L2 to be aligned to leading edge as L1
I'm facing two problems here
When I set my button title programmatically, I want to set my button such that its width grows when text increases and reduces its width when there is less text content. This isn't happening. Please see below screens the constraints I've in place
When I removed my button from superview, I wanted my L2 label Leading to be aligned to L1 leading. So I created a constraint from L2.leading = L1.leading and prioirty is 999
In this case, the button gets reduces its size to almost 0 even if i have text in that. Please advice me setting this up
Problem #1:
use .horizontal UIStackview for the button and text. set its distribution to .fill. For the button set contentCompression resistance priority to .required for .horizontal & set contenHugging priority to .required for .horizontal. So the Button will always wrap the text no matter what.
Problem #2:
While placing inside a stackview, you don't have to remove the button from superview. Just hide it using isHidden.
Code Demonstration
class SampleVC: UIViewController {
private var didAddConstraint = false
// Basic Views
private let label: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
view.text = "Label"
return view
}()
private let topButton: UIButton = {
let view = UIButton()
view.translatesAutoresizingMaskIntoConstraints = false
view.setTitle("Button", for: .normal)
view.setTitleColor(.gray, for: .highlighted)
view.backgroundColor = .green
view.setContentHuggingPriority(.required, for: .horizontal)
view.setContentCompressionResistancePriority(.required, for: .horizontal)
return view
}()
private let rightLabel: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
view.numberOfLines = 0
view.text = "label"
view.backgroundColor = .red
return view
}()
private lazy var stackview: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .horizontal
view.distribution = .fill
view.addArrangedSubview(topButton)
view.addArrangedSubview(rightLabel)
return view
}()
override func loadView() {
super.loadView()
view.addSubview(label)
view.addSubview(stackview)
view.setNeedsUpdateConstraints()
view.backgroundColor = .white
}
override func updateViewConstraints() {
super.updateViewConstraints()
if didAddConstraint == false {
didAddConstraint = true
// top label
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16.0).isActive = true
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
label.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// stackview
stackview.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16.0).isActive = true
stackview.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 8.0).isActive = true
stackview.rightAnchor.constraint(equalToSystemSpacingAfter: view.rightAnchor, multiplier: 16.0).isActive = true
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// TEST Code
// topButton.setTitle("TEST TEST TEST", for: .normal)
// topButton.isHidden = true
}
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
for my app I implemented a collectionview showing cards, that are rotated in a carousel-like animation. My problem is that the cards themselves resize correctly when swiped, but the fonts stay the same size or resize incorrectly. To be specific, the answers (bottommost 5 labels) are in a stackview.
Things I tried:
AutoLayout
Autoshrink
adjustsFontSizeToFitWidth
any number of different constraints
I attached a few screenshots below, where I colored the labels for better visibility.
To change the size of a view and have all of it's subviews / buttons / labels / etc scale with it - including label fonts, you are better off using CGAffineTransform for scaling.
Here is a simple example. It can be pasted into a Playground page to see the effect:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let theStackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.distribution = .equalCentering
sv.alignment = .center
return sv
}()
let theContainerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .green
return v
}()
let btn: UIButton = {
let b = UIButton()
b.setTitle("Tap to Scale", for: .normal)
b.backgroundColor = .red
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
// on button tap, scale the Container view by 50% both ways
// note that Container view's subviews also scale
func btnTapped(_ sender: Any) {
theContainerView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
}
override func viewDidLoad() {
super.viewDidLoad()
// add the button to self.view
self.view.addSubview(btn)
// button position
btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
btn.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20.0).isActive = true
// add a target for the button tap
btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
// add our "Container" view
view.addSubview(theContainerView)
// add our Stack view to the Container view
theContainerView.addSubview(theStackView)
// add 5 labels to the Stack view
for i in 1...5 {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 20.0)
label.text = "This is Label \(i)"
label.backgroundColor = .cyan
label.translatesAutoresizingMaskIntoConstraints = false
theStackView.addArrangedSubview(label)
}
// pin Container view 20-pts from the bottom of the button, and 8-pts from left, right and bottom
theContainerView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 20.0).isActive = true
theContainerView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
theContainerView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
theContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
// pin Stack view 8-pts from top, left, right and bottom of the Container view
theStackView.topAnchor.constraint(equalTo: theContainerView.topAnchor, constant: 8.0).isActive = true
theStackView.leftAnchor.constraint(equalTo: theContainerView.leftAnchor, constant: 8.0).isActive = true
theStackView.rightAnchor.constraint(equalTo: theContainerView.rightAnchor, constant: -8.0).isActive = true
theStackView.bottomAnchor.constraint(equalTo: theContainerView.bottomAnchor, constant: -8.0).isActive = true
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc