Inconsistent Today Widget behavior breaks subview's height constraints - ios

This question is related to another question I just posted on Stackoverflow:
Layout Constraint Conflicts in Default Today Widget
I added a Today Extension as a target to my app, removed the default "Hello World" label that's inside the widget's root view and added a plain UIView in its place. I gave the view a yellow color and pinned it to all edges of the root view - the same way the label was constrained. Then I added another constraint to the yellow view in order to give it a fixed height of 100px.
When I launch the app (tested on simulator and device) that height constraint is simply ignored and the yellow view takes up the whole available space all the way down to the header of the next widget.
When I swipe the notification center up and pull it down again the view suddenly jumps (it seems like it's suddenly "seeing" its own height constraint) leaving a vertical blank space of 39px at the bottom of the widget:
 
I figured that the 39px margin at the bottom is the default bottom margin of a today widget as passed in by the defaultMarginInsets parameter in the widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) method and that I can fix this inconsistent behavior by overriding this method and providing my own margin insets:
func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
var newInsets = defaultMarginInsets
newInsets.bottom = 20
return newInsets
}
However, I would really prefer to use system provided margins instead of fixed values. It seems to me like this is another iOS bug regarding the today widget. Is it? And if not: How can I fix this?

Try avoiding the use of pins.
For positioning, rely on aligning your view to the superview's leading, trailing, top, or bottom edges.
For sizing, try setting your view to have equal heights or widths with the superview. And adjusting the multiplier as needed.
This solved auto layout inconsistencies I was experiencing in a Today Widget.
Updated w/ screenshot:
See above, I am using the align menu (not the pin menu). I select both the view I am trying to constrain, along with the all encasing superview, and tell the prior to share (or, align with) trailing and bottom edges.
I know this is not how Apple may demonstrate it, however it is a workaround that avoids the bugs that occur when using pins with Today Widgets.
Update #2 - And here is all the constraints (including height and width):
The bug must be related to the inferred sizing of a view that is entirely pinned, because when I set the height and width of my view to be relative to its superview (rather than having it be inferred), the bug does not occur.

Related

Autolayout issue with keyboard & UI element heights

Swift 5, Xcode 10
The layout of my UIViewController:
I use this code to push the Server Text Field up when the keyboard is opened.
At first it pushed the bottom UIStackView into the top one, so I added the Server Stack View.top >= Username Stack View.bottom + 20 constraint and now it's keeping a proper distance.
BUT now it also automatically decreases the height of the Server Text Field when it's pushed up. Giving the Server Stack View a fixed height of 60.5 smushes the "Login" button, so I set the height of the Username Stack View to a fixed 110.5, which didn't change anything.
I tried changing the Vertical Content Compression Resistance Priority of multiple UI elements to 999 but there's always one UI element whose height is decreased.
As you can see in this screenshot, there's enough space above the keyboard:
How can I make auto layout use this space instead of "smushing" UI elements?
Edit:
I found out what this additional space is: It's the height of the "version" label and its constraint (30pts to the bottom of the screen in my case). Unfortunately I haven't been able to get rid of this yet - apart from removing the label, which still doesn't stop the "smushing".
The additional empty space above the keyboard equals to the height of the "version" label and its constraint (30pts to the bottom of the screen in my case).
The "squishing" is somehow caused by the centerY - 120 constraint of the UIStackView and even changing the "Content Compression Resistance Priority" didn't work. Plus, if there's a surrounding UIScrollView, it complains about a missing constraint for the "y position" (even if the other constraints are set properly).
Unfortunately the only fix I've found so far that works for both problems is to remove the cause and change the layout. :/
What I ended up with:
A few things worth noting:
The Child View was added to always keep the "version" label at the bottom of the screen - even when the keyboard is active.
The Login View uses the KeyboardLayoutConstraint class for its "bottom" constraint. Without it the scrolling would be completely disabled.
The size of the Stack View is set through the labels and text fields inside.
The two extra UIStackView and UIViews (1x username, 1x server) had to be added to maintain a width of 200 for the UITextFields but still have the surrounding UIStackView and UIScrollView use leading/trailing constraints of 0pts. Without, positioning everything horizontally didn't stick, Xcode complained and the UIScrollView was pretty narrow, which also meant that it wasn't possible to scroll on the sides.
The "top + 100" constraint (Stack View) is visible even when the keyboard is up and the UIScrollView is scrollable. I'm aware it's not pretty but it's the only solution I've found that doesn't stick the text fields to the top of the view, since "middle - 100" doesn't work. It's probably possible to get rid of it by changing the KeyboardLayoutConstraint class.

Solving Storyboard missing trailing/leading constraints warning

I'm shipping my app for all iPhone models (iOS 10.0+) and have made sure that in all localisations, labels and controls will not overlap.
In a simple static cell, it would be enough to give the label on the left side a leading constraint and center it vertically. The same would be done with the control on the right, but with a trailing constraint. All would be fine.
Until Xcode warning.
It asks for missing trailing or leading constraints to avoid overlapping in any case (which would not happen in mine).
Here is a simple test case:
I silenced the warning by giving a switch on the right a leading constraint of 10.0, just to make sure. It works fine. (Attached image first row)
The same, however, with a segmented control would stretch it all the way to the left to the right side of the label. Attached image 3rd row) Even if I increased the size of the label further to the right it would stomp the width of the label.
Since I'd like to have the cell as in the 2nd row, I did what I considered hacky in the view controller, in which case I'd have to specify exact x values for each screen size (which is ok, but I'd like to avoid) (Storyboards, after all...):
#IBOutlet weak var mySegmentedControl: UISegmentedControl!
override func viewWillLayoutSubviews() {
mySegmentedControl.frame = CGRect(x: 238.0, y: mySegmentedControl.frame.minY,
width: 121.0, height: mySegmentedControl.frame.height)
}
Is there a better way to achieve this?
New versions of Xcode will show this as a warning.
You could fix this Adding greater than or Equal constraint to UILabel's trailing.
NB: you can quick fix by clicking on the Yellow Right Arrow near the My Table View Controller Scene text
The UILabel is variable in length. When you set text to label, it will resize automatically. If you are not setting the Trailing Constraint it may overlaps other views (in this case - segmented control). It will work if you add fixed constraint, but new Xcode shows it as warning. So we have to change it as greater or lesser than constraints.

Auto Layout Not So Auto

I have the most basic set up possible. See pic 1:
Believe it or not this is my first project using AutoLayout, I have created everything prior programatically. This basic set up is literally a UIWebView with 1 custom UIView positioned at the bottom. Previously I was using a tool bar that handled everything for me and had no issues with constraints whatsoever. However, the tool bar created discrepancies for event handling when adding a UILongPressGesture to the subview of the UIBarButtonItem so I decided to convert the tool bar to a UIView (Even inserting a UIView into a tool bar, it naturally converts to a button item) for easier handling. But run-time, the view gets pushed off screen by half of the UIView size (48px) See Pic 2. Then when I add buttons, it just gets worse:
I have reviewed the documents and the support HERE with no results. I've spent about 24 hours in total researching it and learned a lot, so my efforts aren't in vein. I KNOW by 'Adding Missing Constraints', the constraints are just recommendations based on the current set up, they aren't concrete in all cases, so I did try to create my own with control drag after reviewing the documents but my set up concluded with no different results, and exponentially more sloppy. So I decided to include the populated constraints by Xcode below :
UIWebView Constraints
Custom UIView (toolBar) Constraints
Any solid starting point recommendations? Does Intrinsic Size have anything to do with it?
EDIT : WORKING CONSTRAINTS I didn't realize you could simply omit a constraint. It seems the culprit was adding one to the top layout guide.
Just for answerer #Matt :
Constant 0 result : there are small gaps at edges
-16 for leading space/trailing space results as a true toolbar emulation in my case with no outstanding warnings or issues. Thanks
Let's talk about the view at the bottom of your interface and how you would use auto layout to position and size it the way a toolbar would be positioned and sized.
To use auto layout, you need to supply sufficient info to determine both position and size. This view is a subview of the view controller's main view. The main view will be resized depending on the screen, so we want to use auto layout to resize the subview ("toolbar") as well. This is what auto layout is for!
So constrain subview leading edge to the leading edge of the superview, and constrain subview trailing edge to the trailing edge of the superview, both with a constant of 0. Now the right and left edges match the superview!
That takes care of horizontal position and size.
Now let's talk about vertical position. The position should be the bottom. So constrain subview bottom edge to the bottom layout guide of the view controller, again with a constant of 0. Now the bottom of the view is at the bottom!
The only thing we don't know is the top of the subview. This, in our situation, is the same as knowing its height. So give the subview a height constraint, set its constant to a reasonable value like 40, and you're done.

Autolayout - anchoring to bottom right

I'm developing an app targetting iOS6/7, and I've lost two hours staring at a storyboard, trying to understand why autolayout doesn't do what I want it to do.
Basically, I have a scene containing a scroll view and in it, I want to have a UIIMage anchored at the bottom right. Therefore, I set four constraints for the image:
Width equals
Height equals
Bottom space to superview and
Trailing space to superview
XCode does not complain about the positioning, so I run my app with confidence, only to find that it is not shown in any orientation. It's just nowhere to be found!
I know that to find how autolayout implemented the constraints and did its magic, I have to inspect the view.bounds rect. I checked at the viewDidAppear event, to find its value to be as expected though:
Image pos is: 0.000000,0.000000 106.000000x103.000000
The frame is of course at the actual position in the storyboard which I guess is to be expected.
Here is a screen cap of my Storyboard:
Any ideas?
Update:
Some more info:
If I remove all constraints and run the app, the image view is shown at the bottom right of my view in portrait, but when rotating, as expected, it is not shown.
Update 2:
This all should fall in the dreaded UIScrollView and AutoLayout threads. In the end I reverted to using a UIView, inside of which is a UIScrollView containing all the content I wanted to scroll (so that no text fields are hidden by the keyboard in landscape mode). The image I wanted anchored at the bottom was left at the container UIView and it all worked as intended.
If you want to anchor to bottom right, Programmatic autoresizingMask has been far more consistent for me.
It's slightly opposite of IB so if you want to anchor bottom right, you want the left margin flexible and the top margin flexible
yourView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
This means that bottom margin, right margin, height and width will all remain the same, keeping it in the lower right of your view.

UIScrollView behaves different depending on fullscreen or not?

When applying auto layout constraints to a UIScrollView, i´m given an error message if the scroll view is laid out to be full screen, but not if it has "margins". I´ll illustrate with 2 examples. In both examples i apply leading and trailing space to the superview and add top and bottom space to layout guide.
Example 1 (behaves as i expect.)
The auto layout constraints are blue, everything is in order
Example 2 (weird behaviour)
I stretch out the scroll view and apply the same rules as in example 1, but now 1 of the constraints ends up different. The "top space to top layout guide" is added as "Vertical space - (-548) - Top layout guide - Scroll View".
And then Xcode complains that i need "constraints for y position or height" for my scroll view.
Can anyone explain why this is?
Unfortunately, I think this is a bug in Interface Builder in Xcode 5. When you try creating a constraint from the top of a view to the top layout guide, IB usually (incorrectly) adds a constraint from the bottom of the view to the top layout guide.
To get around this, try first resizing the scrollView so the top edge of it is much lower down in your viewController:
Then try ctrl dragging from the scrollView to the viewController, and adding a constraint to the top layout guide. You can then select this constraint and adjust the constant in the inspector so that the scrollView aligns with the top of your viewController:
Alternatively, create your constraints in code :)
Just select the scroll view and manually create constraints for the top, left, bottom, and right edges.
What is happening here is that the automatically created constraints don't make sense, in that they do not fully define the size of the scroll view, only its position.
Following your original example with the scrollview smaller than the containing view I noticed that XCode was actually adding one of the constraints wrong in my case, when I selected "Top space to top layout guide" it was adding a "Bottom space to top layout guide" constraint with a large negative value. I can see something similar from your second screenshot.
My solution was to follow your example with the UIScrollView smaller than its container. I then manually dragged out the constraints, resized the UIScrollView to fit the container. The final step was to take the now correct constraints with the wrong constants and set them all to be 0.
Hope that helps, I'm still working on fixing the problem I had but I noticed this stage was similar to what you posted.

Resources