Say I do this ...
override func viewDidLoad() {
super.viewDidLoad()
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
later, I do this ...
view.heightAnchor.constraint(equalToConstant: 51).isActive = true
am I a bad person?
Have I now redundantly added a second constraint (what happens to the first?)
Or does it know to change the first?
Or does something else happen? Am I making a leak?
What should I do, and in what way have I been bad, if any?
What's the correct thing to do here?
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
later, I do this ...
view.heightAnchor.constraint(equalToConstant: 51).isActive = true
am I a bad person?
Yes, and if I'm not mistaken, the runtime will tell you so with a loud message in the console warning that you have unsatisfiable (conflicting) constraints. You have a constraint setting the height to 50 and another setting the height to 51 and both things cannot be true.
You didn't give enough code to be sure of what's going on in your case, but you can easily see that what I'm saying is correct, by making your view controller's code consist only of the following:
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(v)
v.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
v.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
v.widthAnchor.constraint(equalToConstant: 100).isActive = true
v.heightAnchor.constraint(equalToConstant: 50).isActive = true
delay(3) {
v.heightAnchor.constraint(equalToConstant: 51).isActive = true
}
}
}
As you stipulated, we "later" add the 51 constraint — and as I said, the runtime squawks at that moment.
The correct procedure is to keep a reference to the original constraint
so that you can change its constant later. (Under more complex circumstances you could actually deactivate, i.e. remove, the first constraint before adding the second, but if all you plan to do is alter the constant, that's not necessary, as it is mutable.)
Related
OVERVIEW
I'm having trouble getting correct focus order (Accessibility in iOS). It seems like becomeFirstResponder() overwrites my focus order I have specified in the array and causes Voice Over Accessibility functionality to read wrong Accessibility Label first.
DETAILS:
I have a View Controller with containerView. Inside I have UIView of my progress bar image and text input field (placeholder). Both elements have isAccessibilityElement = true attributes and they have been added to my focus order array. However upon screen launch, focus order goes to the input field instead of progress bar UIView.
After extended testing I've noticed that this issue is no longer replicable if I remove below line of code.
otpNumberTextField.becomeFirstResponder()
But this is not a solution. I need cursor in the textfield but Voice Over functionality to read my Progress Bar Accessibility Label first. How to fix it?
SPECIFIC SCENARIO
I've noticed this issue occurs only when I have VC with a last active focus on a Textfield and then transition to the next VC (with a Textfield and a Progress Bar).
Bug is not replicable when I have VC with a last active focus on the Button and then transition to the next VC (with a Textfield and a Progress Bar).
CODE SNIPPET
import UIKit
class MyViewController: UIViewController, UITextFieldDelegate {
var otpNumberTextField = UITextField()
var progressMainDot = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
setupView()
setupBinding()
}
override func viewWillAppear(_ animated: Bool) {
setupView()
textFieldDidChange(otpNumberTextField)
}
func setupView(){
let containerView = UIView()
containerView.backgroundColor = UIColor.init(named: ColourUtility.BackgroundColour)
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
//Progress Bar
let progressBarView = UIView()
containerView.addSubview(progressBarView)
progressBarView.isAccessibilityElement = true
progressBarView.accessibilityLabel = "my accessibility label"
progressBarView.translatesAutoresizingMaskIntoConstraints = false
progressMainDot.image = UIImage(named:ImageUtility.progressMain)
progressMainDot.contentMode = .scaleAspectFit
progressBarView.addSubview(progressMainDot)
//Text Field
otpNumberTextField.borderStyle = UITextField.BorderStyle.none
otpNumberTextField.font = UIFontMetrics.default.scaledFont(for: FontUtility.inputLargeTextFieldStyle)
otpNumberTextField.adjustsFontForContentSizeCategory = true
otpNumberTextField.isAccessibilityElement = true
otpNumberTextField.accessibilityLabel = AccessibilityUtility.enterVerificationCode
otpNumberTextField.placeholder = StringUtility.otpPlaceholder
otpNumberTextField.textColor = UIColor.init(named: ColourUtility.TextfieldColour)
otpNumberTextField.textAlignment = .center
otpNumberTextField.keyboardType = .numberPad
otpNumberTextField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
containerView.addSubview(otpNumberTextField)
otpNumberTextField.becomeFirstResponder()
//Accessibility - focus order
view.accessibilityElements = [progressBarView, otpNumberTextField]
}
//... more code goes here ...
}
If you have already set accessibilityElements, then voice over should respects that order but calling becomeFirstResponder() changes the focus to that text field.
You can try below code, which notifies voice over for shifting the focus to new element due to layout changes.
UIAccessibility.post(notification: .layoutChanged, argument: progressBarView)
So now your modified method should be like below:
func setupView(){
.....
otpNumberTextField.becomeFirstResponder()
//Accessibility - focus order
view.accessibilityElements = [progressBarView, otpNumberTextField]
UIAccessibility.post(notification: .layoutChanged, argument: progressBarView)
.....
}
In my app, I want a three-column UISplitViewController. I create it like this:
let svc = UISplitViewController(style: .tripleColumn)
svc.preferredDisplayMode = .twoOverSecondary
svc.setViewController(TestViewController(), for: .primary)
svc.setViewController(TestViewController(), for: .supplementary)
svc.setViewController(TestViewController(), for: .secondary)
svc.primaryBackgroundStyle = .sidebar
This ViewController that I'm presenting is a really simple viewController. It doesn't do anything except, present a centered red square.
class TestViewController: UIViewController {
private var redView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
redView.backgroundColor = .red
redView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(redView)
redView.widthAnchor.constraint(equalToConstant: 30).isActive = true
redView.heightAnchor.constraint(equalToConstant: 30).isActive = true
redView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
redView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
}
}
Now, when I run this code, I get the following result.
As you can see, 2 of the 3 squares are off-center. Anyone know, what I'm doing wrong here? Or is this a known bug?
You should use "Safe Area" instead of "self.view" when setting to center.
The detail view here has a push-away effect. You just need to click on it and the primary view controller will hide, like below.
In my project I have an array where the user can append elements. My problem right now is that I have another class with a tableView where I want to display all the elements dynamically so if the user adds and element to the array it is also in my tableView.
Class A
var wishListTitlesArray: [String] = [String]()
Class B
var dropDownOptions = [String]() // TableView data -> here I would like to access `wishListTitlesArray`
Update
So thanks for the comments so far, I got the main problem I had but I am still struggling.
My setup:
I create my dropDownButton (which contain a dropDownView) in my ViewController-Class where I also have my var wishListTitlesArray.
I tried dropDownButton.dropView.dropDownOptions = wishListTitlesArray . However that does not do the full job.
#objc func addWishButtonTapped(notification : Notification){
popUpView.popUpTextField.text = ""
self.popUpView.popUpTextField.becomeFirstResponder()
view.addSubview(visualEffectView)
view.addSubview(popUpView)
view.addSubview(wishButton)
self.view.addSubview(dropDownButton)
// constrain blurrEffectView
visualEffectView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
visualEffectView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
visualEffectView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// constrain popUpView
popUpView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
popUpView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50).isActive = true
popUpView.heightAnchor.constraint(equalToConstant: 230).isActive = true
popUpView.widthAnchor.constraint(equalToConstant: view.frame.width - 85).isActive = true
// constrain wishButton
wishButton.centerXAnchor.constraint(equalTo: popUpView.centerXAnchor).isActive = true
wishButton.centerYAnchor.constraint(equalTo: popUpView.centerYAnchor, constant: 70).isActive = true
wishButton.heightAnchor.constraint(equalToConstant: 72).isActive = true
wishButton.widthAnchor.constraint(equalToConstant: 72).isActive = true
// constrain DropDownButton
dropDownButton.centerXAnchor.constraint(equalTo: self.popUpView.centerXAnchor).isActive = true
dropDownButton.centerYAnchor.constraint(equalTo: self.popUpView.centerYAnchor).isActive = true
dropDownButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
dropDownButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
// set the drop down menu's options
dropDownButton.dropView.dropDownOptions = wishListTitlesArray
self.view.bringSubviewToFront(visualEffectView)
self.view.bringSubviewToFront(popUpView)
self.view.bringSubviewToFront(wishButton)
self.view.bringSubviewToFront(dropDownButton)
self.view.bringSubviewToFront(dropDownButton.dropView)
This is where the user can add an element to the wishListTitlesArray:
if let txt = listNameTextfield.text {
self.newListTextfield.resignFirstResponder()
// append user-entered text to the data array
self.wishListTitlesArray.append(txt)
self.wishListImagesArray.append(self.image!)
// DonMag3 - append new empty wish array
self.userWishListData.append([Wish]())
let theCustomWishlistView = createCustomWishlistView()
self.view.addSubview(theCustomWishlistView)
// constrain CustomWishlistView
theCustomWishlistView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 120.0).isActive = true
theCustomWishlistView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
theCustomWishlistView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30.0).isActive = true
theCustomWishlistView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -30.0).isActive = true
theCustomWishlistView.wishlistImage.image = self.image
theCustomWishlistView.wishlistLabel.text = txt
theCustomWishlistView.transform = CGAffineTransform(translationX: 0, y: 1000)
self.view.bringSubviewToFront(containerView)
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: {
(result) in
// scroll to make newly added row visible (if needed)
let i = self.theCollectionView.numberOfItems(inSection: 0) - 1
let idx = IndexPath(item: i, section: 0)
self.theCollectionView.scrollToItem(at: idx, at: .bottom, animated: true)
// close (hide) the "New List" view
self.closeButtonTappedNewList(nil)
})
and the problem right now is that if the user adds an element, dropDownOptions is not updating the data accordingly. So how can I access the updated list? Thanks for all the comments so far!
This is fairly straightforward OO programming. As somebody pointed out, you're dealing with instance variables and instances of classes, not classes.
An analogy:
Instances: If you want one instance of a class to get a value from an instance of another class, it's like having your Toyota car request the radio station presets from your friend's Honda and set your radio the same way.
Classes: The Toyota motor company decides they like the radio presets that Honda uses, so they read the radio station presets from the Honda company and the Toyota factory uses those settings for all new Toyotas.
In class A, make wishListTitlesArray public:
public var wishListTitlesArray: [String] = [String]()
Then, your Class B object will need a pointer to the Class A object. There are various ways to do that. Let's say you set up Class B to take a Class A object at init time:
Class B {
var myAObject: A
var dropDownOptions: [String]
init(aObject: A) {
myAObject = anObject
dropDownOptions = myAObject.wishListTitlesArray
}
//more code here
}
I've started to build my first open source project I would like to share to a world. It's a simple Audio Player view which will work with AudioProvidable protocol and then will play an audio data after it's received.
For now I have an IBDesignable class inherits from UIView which builds itself by defining constraints for its subviews like this:
override init(frame: CGRect) {
super.init(frame: frame)
#if !TARGET_INTERFACE
translatesAutoresizingMaskIntoConstraints = false
#endif
prepareView()
updateUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepareView()
updateUI()
}
func prepareView() {
//Necessary in order to set our own constraints
//btnPlay is already doing the same inside buttonWithImage(_:)
spinner.translatesAutoresizingMaskIntoConstraints = false
sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false
//Add necessary subviews to start setting up layout
addSubview(sliderAudioProgress)
addSubview(lblAudioDuration)
addSubview(lblAudioProgress)
addSubview(spinner)
addSubview(btnPlay)
addSubview(lblTrackNumber)
addSubview(btnNextTrack)
addSubview(btnPreviousTrack)
//Height of play & next/previous track buttons defines AudioPlayerView height
let viewHeight:CGFloat = 48 * 2
//Setup UI layout using constraints
NSLayoutConstraint.activate([
heightAnchor.constraint(equalToConstant: viewHeight),
//Play button constraints
btnPlay.topAnchor.constraint(equalTo: topAnchor),
btnPlay.leadingAnchor.constraint(equalTo: leadingAnchor),
//Spinner constraints
spinner.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
spinner.centerXAnchor.constraint(equalTo: btnPlay.centerXAnchor),
//Progress label constraints
lblAudioProgress.leadingAnchor.constraint(equalTo: btnPlay.trailingAnchor),
lblAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
//Duration label constraints
lblAudioDuration.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -spacing),
lblAudioDuration.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
//Audio Progress Slider constraints
sliderAudioProgress.leadingAnchor.constraint(equalTo: lblAudioProgress.trailingAnchor, constant: spacing),
sliderAudioProgress.trailingAnchor.constraint(equalTo: lblAudioDuration.leadingAnchor, constant: -spacing),
sliderAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
lblTrackNumber.centerXAnchor.constraint(equalTo: centerXAnchor),
btnPreviousTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor),
btnPreviousTrack.trailingAnchor.constraint(equalTo: lblTrackNumber.leadingAnchor, constant: -spacing),
btnNextTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor),
btnNextTrack.leadingAnchor.constraint(equalTo: lblTrackNumber.trailingAnchor, constant: spacing),
lblTrackNumber.centerYAnchor.constraint(equalTo: btnNextTrack.centerYAnchor)
])
//Spinner setup:
spinner.hidesWhenStopped = true
//btnPlay setup:
btnPlay.addTarget(self, action: #selector(AudioPlayerView.playButtonPressed), for: .touchUpInside)
//Slider setup
sliderAudioProgress.thumbTintColor = UIColor(keyFromAppColorPalette: "secondary_color")
sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderValueChanged), for: .valueChanged)
sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderReleased), for: .touchUpInside)
sliderAudioProgress.isEnabled = false
sliderAudioProgress.isContinuous = true
sliderAudioProgress.semanticContentAttribute = .playback
//View's UI effects to make it look better
layer.cornerRadius = 6
layer.masksToBounds = true
layer.borderColor = UIColor.black.cgColor
layer.borderWidth = 2
semanticContentAttribute = .playback
spinner.startAnimating()
btnPlay.isHidden = true
}
As a result that is what we have when audio is receiving:
After the audio received:
In case when there's only one audio to play the bottom part, switches between tracks is redundant. Where do I have to put a checking code for audio tracks count to redesign my UI? I don't want to call prepareView() in didSet {} of audio source array, because I don't want existing elements be added twice.
Regards.
You could add a bool flag to see if the code is running for the first time, or, what I find easier and maybe a little more logical...
func prepareView() {
//Necessary in order to set our own constraints
//btnPlay is already doing the same inside buttonWithImage(_:)
spinner.translatesAutoresizingMaskIntoConstraints = false
sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false
// if our subviews have not yet been added
if sliderAudioProgress.superview == nil {
//Add necessary subviews to start setting up layout
addSubview(sliderAudioProgress)
addSubview(lblAudioDuration)
... etc
}
// continue here with any "every time" setup tasks
}
EnvironmentXcode-8Swift-3iOS-10
IntentThe germain intent is to get three subviews added to the main view, such that they appear sequentially adjacent starting from the top of the screen.There's more to the ultimate intent, but this is where I'm currently stuck.
Working CodeIf I'm just adding a single subview, it works with the following code (slightly abridged, line numbers added for reference):
01: class ViewController: UIViewController, UITextFieldDelegate {
02: var textMatte: UIView!
03: override func viewDidLoad() {
04: super.viewDidLoad()
05: setupTextfieldMatte(inView: view)
06: }
07: func setupTextfieldMatte(inView: UIView) {
08: textMatte = UIView()
09: textMatte.backgroundColor = UIColor.lightGray
10: textMatte.translatesAutoresizingMaskIntoConstraints = false
11: inView.addSubview(textMatte)
12: let tmGuide = textMatte.layoutMarginsGuide
13: let inGuide = inView.layoutMarginsGuide
14: tmGuide.topAnchor.constraint(equalTo: inGuide.topAnchor, constant: 40).isActive = true
15: tmGuide.leadingAnchor.constraint(equalTo: inGuide.leadingAnchor, constant: 20).isActive = true
16: tmGuide.trailingAnchor.constraint(equalTo: inGuide.trailingAnchor, constant: -20).isActive = true
17: tmGuide.heightAnchor.constraint(equalToConstant: 40).isActive = true
18: }
19: //... other code ...
20: }
Broken CodeI wanted to break-up the creation step from the constraint step, in order to do "batch" processing (and because there's more that needs to happen, as eventually these subviews will also have their own subviews...). Added relative line numbers for ease of further description and (hopefully) answers from you folks:
01: class ViewController: UIViewController, UITextFieldDelegate {
02: var mattes = [UIView()]
03: override func viewDidLoad() {
04: super.viewDidLoad()
05: for (index, title) in ["A", "B", "C"].enumerated() { // genericized for posting
06: let v = createView(idx: index)
07: mattes.append(v)
08: view.addSubview(v)
09: }
10: _ = mattes.popLast() //from testing/debugging found that there was an extraneous entry in the array
11: addViewAnnotations(views: mattes, inView: self.view)
12: }
13: func createView(idx: Int) -> UIView {
14: let v = UIView()
15: v.backgroundColor = UIColor.lightGray
16: v.translatesAutoresizingMaskIntoConstraints = false
17: v.tag = idx
18: return v
19: }
20: func addViewAnnotations(views: [UIView], inView: UIView) {
21: let inGuide = inView.layoutMarginsGuide
22: for (index, v) in views.enumerated() {
23: let myGuide = v.layoutMarginsGuide
24: if index == 0 {
25: myGuide.topAnchor.constraint(equalTo: inGuide.topAnchor, constant: 40).isActive = true
*** //^^^ libc++abi.dylib: terminating with uncaught exception of type NSException
26: }
27: else {
28: myGuide.topAnchor.constraint(equalTo: views[index-1].layoutMarginsGuide.bottomAnchor, constant: 10).isActive = true
29: }
30: myGuide.leadingAnchor.constraint(equalTo: inGuide.leadingAnchor, constant: 20).isActive = true
31: myGuide.trailingAnchor.constraint(equalTo: inGuide.trailingAnchor, constant: -20).isActive = true
32: myGuide.heightAnchor.constraint(equalToConstant: 40).isActive = true
33: }
34: }
35: //... other code ...
36: }
The error occurs on line 25 (error message in comment below that line), verified with print statements above and below the line.There's no additional error/debugging information that I can seeI had expected that it might break on line 28, but 25 (in the "broken code") is identical to line 14 (in the working code)Note: If I put a try in from of the line, Xcode tells me: "No calls to throwing functions occur within 'try' expression"
I have a feeling that this is one of those stupid oversights that crop up so often when modifying code, but I just cannot seem to spot the source of the problem - so I'm hoping one of you can.
UPDATE 2016-10-16Based on my modification as described in the first comment I added, I played around with the code a bit more and found that the problem occurs only when trying to make [what is now] the last constraint active:
001: override func viewDidLoad() {
002: super.viewDidLoad()
003: for (index, title) in ["Small Blind", "Big Blind", "Ante"].enumerated() {
004: let v = createView(idx: index)
005: mattes.append(v)
006: view.addSubview(v)
007: let myGuide = v.layoutMarginsGuide
008: let inGuide = view.layoutMarginsGuide
009: myGuide.leadingAnchor.constraint(equalTo: inGuide.leadingAnchor, constant: 20 ).isActive = true
010: myGuide.trailingAnchor.constraint(equalTo: inGuide.trailingAnchor, constant: -20).isActive = true
011: myGuide.heightAnchor.constraint(equalToConstant: 40).isActive = true
012: if index == 0 {
013: myGuide.topAnchor.constraint(equalTo: inGuide.topAnchor, constant: 40).isActive = true
014: }
015: else {
016: let x = myGuide.topAnchor.constraint(equalTo: mattes[index-1].bottomAnchor, constant: 10)
017: x.isActive = true
//^^^ libc++abi.dylib: terminating with uncaught exception of type NSException
018: }
019: }
020: }
All the other constraints have no issues. It doesn't seem to matter if I use:mattes[index-1].bottomAnchormattes[index-1].layoutMarginsGuide.bottomAnchorThe first one evaluates to <NSLayoutYAxisAnchor:0x174076540 "UIView:0x100b0d380.bottom">, the second to <NSLayoutYAxisAnchor:0x174076c00 "UILayoutGuide:0x174191e00'UIViewLayoutMarginsGuide'.bottom"> (if that matters?)If I comment out line 017, the code runs, but while the first rectangle is in the correct location, the remaining two are not - they're flush against the top of the screen instead of below the top indicators.So - perhaps there's a better way to set them up?
What is the description of the exception? Be sure to add views to their superview (in the same view hierarchy) before adding the constraints. You can't create constraints between views that aren't both in the same hierarchy.
In your update, you're using index-1 as an index into the mattes array, that may be the cause of the exception the first time around.
I don't know if this is contributing to issues, but I don't create constraints from one view's layoutMarginsGuide to a superview's layoutMarginsGuide. Think of that guide as for the view and it's subviews. So the constraints would be from the subview itself (not its margin) to the superview's layoutMarginsGuide.
I think it was a combination of things that was causing the problem, but first let me present the working code and a couple of screen shots
001: class ViewController: UIViewController, UITextFieldDelegate {
002: var textfields: [UITextField] = []
003: var labels: [UILabel] = []
004: var mattes: [UIView] = []
005: override func viewDidLoad() {
006: super.viewDidLoad()
007: view.backgroundColor = UIColor.blue
008: for title in ["A", "B", "C"] {
009: let matte = setupMatte(inView: view)
010: let textfield = setupTextfield(inView: view, title: title)
011: let label = setupLabel(inView: view, title: title)
012: mattes.append(matte)
013: textfields.append(textfield)
014: labels.append(label)
015: }
016: addConstraints(inView: view) // could probably leave off parameter, but for now...
017: }
018: func setupMatte(inView: UIView) -> UIView {
019: let matte = UIView()
//...various view attribute configurations...
020: matte.translatesAutoresizingMaskIntoConstraints = false
021: inView.addSubview(matte)
022: return matte
023: }
024: func setupTextfield(inView: UIView, title: String) -> UITextField {
025: let textfield = UITextField()
//...various textfield attribute configurations...
026: textfield.translatesAutoresizingMaskIntoConstraints = false
027: inView.addSubview(textfield)
028: return textfield
029: }
030: func setupLabel(inView: UIView, title: String) -> UILabel {
031: let label = UILabel()
//...various label attribute configurations...
032: label.translatesAutoresizingMaskIntoConstraints = false
033: inView.addSubview(label)
034: return label
035: }
036: func addConstraints(inView: UIView) {
037: for (index, matte) in mattes.enumerated() {
038: let textfield = textfields[index]
039: let label = labels[index]
040: matte.leadingAnchor.constraint(equalTo: inView.leadingAnchor).isActive = true
041: matte.trailingAnchor.constraint(equalTo: inView.trailingAnchor).isActive = true
042: matte.heightAnchor.constraint(equalToConstant: 50).isActive = true
043: label.leadingAnchor.constraint(equalTo: matte.leadingAnchor, constant: 20).isActive = true
044: label.widthAnchor.constraint(equalToConstant: 150).isActive = true
045: textfield.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 5).isActive = true
046: textfield.trailingAnchor.constraint(equalTo: matte.trailingAnchor, constant: -20).isActive = true
047: if index == 0 {
048: matte.topAnchor.constraint(equalTo: inView.topAnchor, constant: 20).isActive = true
049: }
050: else {
051: matte.topAnchor.constraint(equalTo: mattes[index - 1].bottomAnchor, constant: 5).isActive = true
052: }
053: textfield.centerYAnchor.constraint(equalTo: matte.centerYAnchor).isActive = true
054: label.centerYAnchor.constraint(equalTo: textfield.centerYAnchor).isActive = true
055: }
056: }
//... other code ...
057: }
Interestingly, while the image from the simulator correctly handles landscape mode, when I run it on my iPhone 6s - it doesn't rotate when I hold the phone in landscape mode - but I'm not too worried about that at this time, since the app I'm working on is only intended for portrait mode.
Now, the pieces that I think made a significant difference from my original code:
(Note: I don't know why, but for some reason I originally named the function for handling constraints "Annotations", this was fixed in my current working code)
BROKEN : var mattes = [UIView()]
WORKING: var mattes: [UIView] = []
I think the above may have been a significant difference
BROKEN : viewDidLoad function looped:
Created sub-view
Appended sub-view to mattes array
Called view.addSubview(v)
After loop:
Popped off last element of mattes array
Added annotations
WORKING: viewDidLoad function looped:
Created sub-view (matte)
The creation process called inView.addSubview(matte) (where inView is the same as view or self.view)
Appended sub-view to mattes array
After loop:
Added annotations
I think the difference in how mattes was defined eliminated the need to pop off the last (or any) element in the array before going to add the constraints
BROKEN : in addViewAnnotations I made constraints using the layoutMarginGuide of both the sub-view and [parent] view
v.layoutMarginGuide.topAnchor.constraint(equalTo: view.layoutMarginGuide.topAnchor, constant: 40).isActive = true
WORKING: in addConstraints I made the constraints directly from the sub-view and [parent] view
matte.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
(Thanks to Jerry for pointing this out, I didn't understand the distinction and I don't think I fully fixed my code when I did my "UPDATE")
The above was probably the most significant difference