Autolayout: UIView frame wrong after rotation - ios

I have created my own implementation of a UITabBar (just a UIView instance).
This UIView contains 3 TabBarItem instances. A TabBarItem is a UIView subclass, and each contains the following UI controls:
UIImageView
UILabel
TabBarBadge (custom UIView subclass)
I am laying out the view hierarchy in a storyboard using auto layout.
The badge for a tab bar item should be positioned so that the y-center of the badge is aligned with the top edge of the image view, and it should be aligned to the right side of the image view, with a 4 point overlap (so, left edge of badge aligned with right edge of image view, with a constant of -4).
This works fine when the app loads, whether in portrait or landscape mode. In either case, though, rotating the device ends up with the frame of the badge in the wrong place after rotation finishes.
Here's a printout from the console of the NSLayoutConstraint in question:
<NSLayoutConstraint H:[UIImageView]-(-4)-[TabBarBadge] (active)>
That's exactly what I would expect it to be.
Here's a printout from the console of the TabBarBadge in question (portrait mode; correct presentation):
<TabBarBadge frame = (80.6667 0; 36 20); text = '67'; autoresize = RM+BM>
Here's a printout from the console of the TabBarBadge in question (landscape mode; incorrect presentation):
<TabBarBadge frame = (80.6667 0; 36 20); text = '67'; autoresize = RM+BM>
So, you can see that the frame isn't changing/updating when the rotation occurs.
Here are a couple of screen shots:
Portrait, Correct Display
Landscape, Incorrect Display
Constraints (Printout of constraints above is from this view)
So, what has me confused is that the rest of the TabBarItem is updating correctly. The UIImageView and UILabel are automatically updating correctly. Why is this one frame not updating correctly?

So, first I researched whether a custom drawRect: method might be the cause of my problem. That doesn't appear to be a factor.
Then, I started noticing some odd discrepancies between the view's frame at different times, and how only this particular one (there are actually 3 in the app) was having the problem. The other 2 currently have no values displayed.
Then, I remembered that I use a UIDynamicAnimator when the value is changed to "bounce" the view, in order to bring attention to the new value.
So, the problem is actually that I wasn't "releasing" (in the control sense, not the memory sense) the view from the UIDynamicAnimator when I was done with the animation. (I'm still not sure whether that's a bug or not. It seems to me that if the animation is paused and the constraints should update the view's frame, then perhaps that should occur.)
So, here's the actual code that was "buggy":
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator {
// {Other code}
}
And here's the code that fixed my issue:
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator {
// {Other code}
// Code that fixed the issue:
[self.animator removeAllBehaviors];
}
If I need to animate the view again, I just re-create the animator anyway, so this seems to be the best fix.
This appears to be in agreement with Matt's answer here. (I didn't know this was an issue with UIDynamicAnimator when I asked the question.)

Related

Parent UIView in StackView displayed wrong, but its subviews are correct

I have a problem where the frame/layer is shown correctly on iPhone, but not on iPad and iPhone X. Only after going to another view and back to that UIViewController it is shown correctly.
I have a 3 UIViews in a vertical StackView. The first top 2 are shown correctly. But the third is not. The third UIView contains a containerView and in it there is a TableView. Through the view debugger I can see that the containerView and the tableView have the correct frame, but it looks like the parent UIView is not redrawn correctly.
Here is a screenshot of the view debugger from the front of that UIView. The UIView with the red background is the one displayed incorrect (correct after going away and back to that UIViewController).
UIView with red background incorrect (Frame is correct, but displayed wrong). The subviews have the correct frame. I set a backgroundcolor to those subviews and these are redrawn correctly
Here the same view from the back:
UIView in question from behind
I tried .setNeedsDisplay., .setNeedsLayout, followed by .setLayoutIfNeeded. Running those in main thread, but didn't got success so far.
I am using Swift 4 and iOS 11. As I said this is in a vertical StackView. The first 2 UIViews have a height constraint, this one, has no constraint as it should fill the rest.
Any help appreciated!
The problem was solved for iPad and iPhone X, removing the shadow layer on the view that was drawn incorrectly.

Autolayout causing UI Elements to "snap" into place at runtime?

I have a UIView with a UIImageView at the very top, a UILabel below it, a UIButton below that, and a UISegmentedControl that determines what determines what embedded UIview to display at the bottom (which also a choice to not show any at all).
I've run into the problem where I've set up all of my constraints in the interface builder, and everything seems to be fine when I switch between screen sizes in the storyboard. However when I actually run the project on a device or emulated, the UIimage at the top is briefly stretched before "snapping" into a size the fits the constraints. Also, it seems as if the label disappears for a brief second and reappears after the image has snapped into a size. After the "snap" has occurred, everything is in place and there are no problems.
This snapping occurs both when testing on a 4 and 3.5 inch display. I find this odd because I've designed the UI for the 4inch screen perfectly.
Does anyone know why this is happening?
Edit
Here's whats the constraints look like in IB.
This is potentially due to adjustments that you might be making to your UI elements (or constraints) from the view controller in code. For example, if you are programmatically setting a different UIImage into your UIImageView, and this code is happening too late in the view lifecycle (for example, in viewDidAppear) after a layout pass has already calculated view positions and sizes, then you will see a visible snap as views take on new positions based on the new intrinsic content size of the image view.
This could be caused by other adjustments such as injecting a localized string into a UILabel in code, which causes the label to have a smaller or larger intrinsic content size, which in turn affects the layout based on your constraints.
If you are making adjustments to your UI in code, make sure they are happening in viewDidLoad or viewWillAppear: so that they occur before the view's initial layout pass (and the view's animation onscreen).
If you're still seeing issues, you can try explicitly forcing an immediate layout pass to occur on the view controller's view at the end of viewWillAppear: by doing the following:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.view setNeedsLayout];
[self.view layoutIfNeeded]; // forces an immediate layout pass
}
Spacing between the UIElements might be the problem . Specially when using Pickers
Got it, You have applied constraint for Picker to adjust along with segmented control. So when app runs on 4 inch screen , picker automatically fits properly, but for 3.5 Inch picker always starts from bottom of the screen and picker will push segmented control upwards and hence segmented control will push rest control automatically.
Remove picker and segmented control constraint.

Unresponsive area in UITableView in landscape orientation

I'm developing an iPad application where I have a screen with UITableView hosted on UIScrollView. UITableView's leading space and trailing space is being pinned to superview.
I'm using Autolayout in my project.
When this screen appearing in landscape mode or rotated to it all visible elements of UITableView being resized correctly. However area after x=768pt is being untouchable. It's definitely area because beginning of editable elements (text view and text field) are being touchable.
I checked contentSize of table, it's being OK (1024 in my case).
Also when I rotate iPad from landscape to portrait contentSize of table still stays landscape (1024). I tried solution from this topic: bug in UITableView layout after orientation change and it worked. Don't know if this is connected to my issue.
I tried to place regular view instead of my UITableView. On this view after rotation touch area was OK. However when I placed my table on this view right area still was unresponsive.
Table's property clipsToBounds set to YES so it seems that content is not becaming outside of UITableView.
Please point to what may cause this issue. Right now I'm completely out of ideas what may cause this.
I found a solution. The problem was in overriden method -(void)layoutSubviews in my custom cell. I simply forgot to call [super layoutSubviews] in it.
Never forget to call [super layoutSubviews] in any non-standard view.
Hope my solution will help someone not to waste hours on simple issue.

UIButtons non-responsive after being moved in from off iPad screen

The description might be a bit confusing, I've added pictures to try to illustrate what I'm describing. Please let me know what I can clarify to help.
I have an iPad application with a main view that is a xib. The size of the xib is 1024 by 1384 and is meant to be viewed in landscape mode. There is a row of buttons that are visible at the bottom of the iPad screen. When one of the buttons is pressed I move the frame so that these buttons are now at the top of the visible portion of the screen. There are additional elements that start out offscreen but then come onscreen after the move.
The problem I'm having is that the UIButton that starts offscreen is not calling the IBAction associated with it.
I have tried to setUserEnabled to YES for it but that doesn't seem to be making any difference either. I've also tried setNeedsDisplay after the animation is complete.
Anyone have any ideas?
When you add the view to the screen, it changes the view's size to fit the visible portion on the screen. As such, your frame is smaller than the total content area of the view. Moving the frame won't do anything for you; it will move the existing visible content up, but it won't change the view to show new visible content.
Instead, you want to be changing the view's bounds.origin, which will change the visible portion of the view's content.
Edit:
Note that even though the view was shrunk, I suspect that the clipsToBounds property on your main view was set to NO. That means that it will actually continue displaying content outside of the bounds, which is why it shows up. However, hit-testing only works on the actual frames of the view. All that stuff that shows up outside the bounds is still visible, but it's not interactible.

UIView coordinate transforms on rotation during keyboard appearance

iPad app; I'm trying to resize my view when the keyboard appears. It amounts to calling this code at appropriate times:
CGRect adjustedFrame = self.frame;
adjustedFrame.size.height -= keyboardFrame.size.height;
[self setFrame:adjustedFrame];
Using this technique for a view contained in a uisplitview-based app works in all 4 orientations, but I've since discovered that a vanilla uiview-based app does not work.
What happens is that apparently the uisplitview is smart enough to convert the coordinates of its subviews (their frame) such that the origin is in the "viewer's top left" regardless of the orientation. However, a uiview is not able to correctly report these coordinates. Though the origin is reported as (0,0) in all orientations, the view's effective origin is always as if the ipad were upright.
What is weird about this is that the view correctly rotates and draws, but it always originates in the literal device top left. How can I get the view to correctly make its origin the "top left" to the viewer, not the device's fixed top left? What am I missing? Please, for something so trivial I've spent about 6 hours on this already with every brute force technique and research angle I could think of.
This is the original source which doesn't work in this case:
move up UIToolbar
OK, I don't know what the ACTUAL answer is to the original question, but I can say with certainty that one way to resolve the issue is to always ensure that you don't manipulate a viewController's view directly. Always wrap your view inside a container view inside the main "view", then have that container view adjust its position etc as needed. Works exactly as the splitview does, probably because in both cases now the view in question is a subview of the main "view". What a relief!

Resources