In an attempt to solve an Auto-Layout issue related to programmatically adding sub views to a scroll view, I have run into many references throughout the internet that, in various scenarios say to set translatesAutoresizingMaskIntoConstraints = YES or translatesAutoresizingMaskIntoConstraints = NO, depending on the case.
However, in Swift, when I type:
var view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
I get the in-line error: Cannot assign to 'translatesAutoresizingMaskIntoConstraints' in 'view'. Why? Because, when inspected, you'll find that it's a parameterless function, not a property.
I've gotten around this by subclassing, but it's a major inconvenience to have to subclass every view I'm dealing with, just to set translatesAutoresizingMaskIntoConstraints:
class CardView: UIView {
override func translatesAutoresizingMaskIntoConstraints() -> Bool {
return false
}
}
Does anyone know a way around this, or can shed light on the discrepancy between what the general internet councils tell you and what you can actually do, in Swift?
translatesAutoresizingMaskIntoConstraints is actually a method on UIView and not a property.
The syntax works because ObjC lets you use dot-notation for calling methods as well (there's a whole other discussion on how properties actually auto-generate getter/setter methods).
Use the method instead of trying to use the property notation from ObjC
view.setTranslatesAutoresizingMaskIntoConstraints(false)
Use view.setTranslatesAutoresizingMaskIntoConstraints(false) instead.
Swift 2
view.translatesAutoresizingMaskIntoConstraints = false
If you create any views in code like text views, buttons, labels, etc.
You need to be careful how you add Auto Layout constraints to them. The reason for this is that iOS creates constraints for you that match the new view's size and position, and if you try to add your own constraints these will conflict and your app will break.
Lets take an example for a uilabel:
let titleLabel = UILabel()
titleLabel.translatesAutoresizingMaskIntoConstraints = false
Likewise in your case:
let newView = UIView(frame: CGRectZero)
addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
Related
I have define a custom actions in my accessibilityElement:
UIAccessibilityCustomAction *action1 = ...initWithName:#"label1";
UIAccessibilityCustomAction *action2 = ...initWithName:#"label2";
element.accessibilityCustomActions = #[action1, action2];
When swipe down/up, it reads "Drag" in addition to the normal "label1", "label2", what is this "Drag" and how did it come about?
For anyone that comes along and sees this, I was having the same issue, and I was able to find that Apple added (sometime around iOS 11) the UITextDraggable protocol to UITextView.
This is defined as:
The interface that determines if a text view is a drag source.
This protocol has the property textDragInteraction that defaults to true for UITextViews.
You can set this property to false like this:
Swift:
textView.textDragInteraction?.isEnabled = false
Obj-C:
textView.textDragInteraction.enabled = NO;
Hello. I am building flights booking app, which has complex UITableViewCell. Basically, it is simple card with shadow, that has bunch of stackviews. First stackview, you see it on image is for labels. It is horizontal and dynamic. The next stackview shows flights. It has complex custom view, but for the sake of simplicity, it is shown with green border. It is also dynamic, so I need separate stackview for it. The next stackview is for airline companies that can handle this booking. I call them as operators. It is also dynamic, so I build yet another stackview for them. And of all these stack views are inside some core stackview. You can ask, why I created separate stack views instead of one? Because, labels above can be hidden. And also spacing in all stackviews are different.
It is really complex design. I followed above approach and build UITableViewCell. But performance is really bad. The reason is simple: I do too many stuff in cellForRowAt. The configure method of UITableViewCell is called everytime when the cell is dequeued. It means I should clean my stackview every time and after only that, append my views. I think it is really affects performance. I don't tell about other if/else statements inside cell. The first question is how can I increase scrolling performance of UITableViewCell in this case?
Some developers reckons that UITableView should be killed. UICollectionView rules the world. OK, but can I use UICollectionView with this design? Yes, of course, but above card would be one UICollectionViewCell and I simply don't avoid problem. The another solution is to build separate UICollectionViewCell for label (see on image), flight and operator. This would definitely increase performance. But, how can I make all of them live inside card?
P.S. What is inside my cellForRowAt method? There is only one configure method and assigning values to closure. But configure method is pretty complex. It gets some protocol which has bunch of computed properties. I pass implementation of that protocol to configure method. Protocol is like this:
protocol Booking {
var flights: [Flight] { get }
var operators: [Operator] { get }
var labels: [Label] { get }
var isExpanded: Bool { get set }
}
Implementation of this protocol is also complex. There are bunch of map functions and if/else statements. Some string manipulations. So, does that cause a problem? How can I solve it? By avoiding properties to be computed and just pass properties(flights, operators) to the implementation?
As I said in my comment, without seeing complete detail, it's tough to help. And, it's a pretty broad question to begin with.
However, this may give you some assistance...
Consider two cell classes. In each, the "basic" elements are added when the cell is created -- these elements will exists regardless of actually cell data:
your "main" stack view
your "labels" stack view
your "flights" stack view
your "operators" stack view
To simplify things, let's just think about the "operators" stack view, and we'll say each "row" is a single label.
What you may be doing now when you set the data in the cell is something like this...
In the cell's init func:
// create your main and 3 sub-stackViews
Then, when you set the data from cellForRowAt:
// remove all labels from operator stack
operatorStack.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
// add new label for each operator
thisBooking.operators.forEach { op in
let v = UILabel()
v.font = .systemFont(ofSize: 15)
v.text = op.name
operatorStack.addArrangedSubview(v)
}
So, each time you dequeue a cell in cellForRowAt and set its data, you are removing all of the "operator" views from the stack view, and then re-creating and re-adding them.
Instead, if you know it will have a maximum of, let's say 10, "operator" subviews, you can add them when the cell is created and then show/hide as needed.
In the cell's init func:
// create your main and 3 sub-stackViews
// add 10 labels to operator stack
// when cell is created
for _ in 1...10 {
let v = UILabel()
v.font = .systemFont(ofSize: 15)
operatorStack.addArrangedSubview(v)
}
Then, when you set the data from cellForRowAt:
// set all labels in operator stack to hidden
operatorStack.arrangedSubviews.forEach {
$0.isHidden = true
}
// fill and unhide labels as needed
for (op, v) in zip(thisBooking.operators, operatorStack.arrangedSubviews) {
guard let label = v as? UILabel else { fatalError("Setup was wrong!") }
label.text = op.name
label.isHidden = false
}
That way, we only create and add "operator views" once - when the cell is created. When it is dequeued / reused, we're simply hiding the unused views.
Again, since you say you have a "really complex design", there is a lot more to consider... and as I mentioned you may need to rethink your whole approach.
However, the basic idea is to only create and add subviews once, then show/hide them as needed when the cell is reused.
I would like to create multiple UIViews that can be reproduced by using a single function. I have a UIView that is placed on a storyboard and connected to my class with an IBOutlet:
#IBOutlet weak var contentView: UIView!
I have a function that loads a xib into my UIView:
func createView(layoutConstant: CGFloat) {
if let customView = NSBundle.mainBundle().loadNibNamed("TestView", owner: self, options: nil).first as? TestView {
contentViewTopLayoutConstraint.constant = layoutConstant
contentView.addSubview(customView)
}
}
I am now trying to add two of them to my view, but only one shows up:
createView(0)
createView(70)
Any ideas?
I think both views are added, although they happen to be in the same spot, so it looks like there is only one! A quick and dirty way to verify that would be updating your createView method with this line:
contentView.frame.origin.y = layoutConstant
Basically your contentViewTopLayoutConstraint is not connected to the views you are creating, so setting its constant value will not have any impact.
Because frames for all those views will of same size. Origin(x,y) will be same for all the views, so they are overlapping one on another and you can only see the top one view.
In your code example it looks like you're only setting a layout constraint on the contentView you are placing your two new views inside of. What you need to do is set layout constraints on the two views your are placing inside in relation to their superview i.e. the contentView.
Basically, add the layout constraints to the customView views.
its quite simple.. iterate a loop by creating uiview along with adding those into the array and customize your particular view by getting them using array index.
Happy code ..
I'm facing one issue while updating top constraint dynamically.
I have one subview1 added over viewcontroller1 view in its xib file and i have given topview constraint to subview1 as 65 and created an outlet for it.
Now i have added viewcontroller1 view over viewcontroller2. After adding it i'm trying to update the constant value of the constraint to 108. But its not getting reflected.
In viewcontroller1 i'm doing
self.topErrorViewConstarints.constant = 108
self.view.updateConstraints()
Any idea why its not getting reflected?
You need to layout the view again.
self.view.layoutIfNeeded()
Try this:
self.topErrorViewConstarints.constant = 108
self.view.setNeedsUpdateConstraints()
self.view.layoutIfNeeded()
It should work. If not then you made mistake somewhere else.
That's not how updateConstraints() supposed to work.
You can, of course, modify any constraints then update layout like this:
self.topErrorViewConstarints.constant = 108
self.view.layoutIfNeeded()
But if you want to implement updateConstraints() you need the following:
// Call when you need to update your custom View
self.view.setNeedsUpdateConstraints()
Then inside the View override the function which will be called automatically by the system:
override func updateConstraints() {
super.updateConstraints()
topErrorViewConstarints.constant = currentTopErrorConstraint()
}
So, updateConstraints() is better suited for custom views with inner layout, that you don't want to be modified from outside. Also, it's good when you need to update many constraints at once.
Problem:
I have faced a similar problem in UiKit code. I created a view programmatically and the add constraint like below:
myView?.heightAnchor.constraint(equalToConstant: oldValue).isActive = true
It worked fine for the first time when the view is created. But need to update the height for some events, so I did like this:
myView?.heightAnchor.constraint(equalToConstant: oldValue).isActive = false
myView?.heightAnchor.constraint(equalToConstant: newValue).isActive = true
But the newValue had no effect, I also try with:
parentOfMyView.layoutIfNeeded()
It was also a failed attempt.
Solution:
I created a global variable to store the constraint:
private var viewHeightAnchor: NSLayoutConstraint?
Then use that when needed:
viewHeightAnchor?.isActive = false
viewHeightAnchor = myView?.heightAnchor.constraint(equalToConstant: 80.0)
viewHeightAnchor?.isActive = true
In this way I also didn't have to call layoutIfNeeded()
Is it possible to set the Frame before ViewDidLoad() in a UIViewController?
Currently, I do this in the constructor:
View.Frame = _frame;
The problem is that this call automatically triggers first the ViewDidLoad() function.
In ViewDidLoad() I place my subviews dependant on the "View.Frame" property.
But this property only changes after ViewDidLoad() was completed.
So basically "View.Frame = _frame;" first trigger "ViewDidLoad()" with the old "Frame" value and only after that the property is changed.
I can solve this by first assigning a class variable to "_frame" and use that in "ViewDidLoad()", but I would like to see if there is another solution and if I'm missing something here.
(I'm also missing the idea behind this flow)
This is a common situation on iOS, and I would say you are doing the right thing for your constructor. Calling the getter on the View property will load the view, so they provide an IsViewLoaded property to check for this.
If it is not a constructor where you are passing in the frame, you could also do something like this in your controller:
public void SetFrame(Frame frame)
{
if (IsViewLoaded) {
View.Frame = frame;
else {
_frame = frame;
}
}
Because of this scenario, it might be worth thinking about your design. I don't know the full situation, but could your controller decide its Frame within the class? Maybe you could pass another value in that would let you do the math for Frame in ViewDidLoad, and that would match Apple's pattern a little better.