Embedding Intrinsically Sized View Breaks Animation - ios

I have built a test project to show what the goal is vs. what I currently have happening. The gif on the left shows exactly what I want the ending appearance to be. It is constructed with a single traditional view hierarchy. I need to achieve this with the pink view being an embedded/contained view. My attempts so far have only gotten me to the gif on the right.
The way the (pink) contained view grows is possibly an important detail: the blue subview changes it's height, and the whole apparatus gets a new intrinsic size because of all the connected vertical constraints. As you would expect, this is a simplification of my actual app, but I think it has all the important bits.
The main things I see that are strange:
The yellow/orange "other" view is not animating at all.
The pink contained view is animating nicely for it's own part, but it is animating it's position, even though it's frame has the same origin before and after the animation as shown here:
Here is the Storyboard of the right gif. Both the container view in the "parent" scene and the top view in the "child" scene have translatesAutoresizingMaskIntoConstraints set to false with runtime attributes.
The question then: **What must I change about my configuration to get all affected layout changes to animate (properly) when I have a size change in an intrinsically-sized and contained view? **
Edit: Tried manual embed
Since posting the question, I have tried a manual View Controller Containment strategy, and I got the exact same results as with the Storyboard technique, which is ultimately a good sign for the platform. There was 1 fewer view in the total hierarchy, but it didn't seem to make a difference.
Edit: Bounty and project
I have added a 100 point bounty to attract attention. I have also uploaded my sample project to this github repo. Check it out!

Changing your animation block in InnerViewController as follows does the trick.
var isCollapsed = false {
didSet {
let factor:CGFloat = isCollapsed ? 1.5 : 0.66
let existing = innerViewHeightConstraint.constant
UIView.animate(withDuration: 1.0) {
self.innerViewHeightConstraint.constant = existing * factor
self.view.layoutIfNeeded()
self.parent?.view.layoutIfNeeded()
}
}
}
The key difference is self.parent?.view.layoutIfNeeded(), which tells the embedding view controller to update the constraints as part of the animation, instead of immediately before the start of the animation.

Related

UIGestureRecognizer not called after setting UIViews frame inside UIScrollView

i am having some very strange issue. Basically i am implementing a drag and drop view with a snap to the horizontal 1D grid. Well, so when i drag a view and its center coordinate X is bigger or smaller then a different view, the non-dragged view should be animated to the left or right of its original position.
This works fine most of the time. But in some special cases its not working. In some situations specific situations the view does not receive any gesture callbacks anymore. I can reproduce this issue and have found out that when i remove the code that applies the animation, everything its working fine.
Basically this is the code that is called when the dragged view is at a position where the view below should be moved to the left or right
/**
* Animate element to its saved position
*/
- (void)switchElement:(unsigned int)draggedIndex with:(unsigned int)otherIndex
{
// first animate
UIView *view = views[draggedIndex];
UIView *otherView = views[otherIndex];
// IF I COMMENT THIS OUT, EVERYTHING WORKS FINE
otherView.frame = [self getImageRectForIndex:draggedIndex];
// now switch internally
if(draggedIndex != otherIndex)
{
// switch views
views[draggedIndex] = otherView;
views[otherIndex] = view;
}
}
Any idea if there is something to have in mind if i animate UIViews and have gesture recognizers attached to them?
If somebody is willing, i can paste the whole class here to test it.
SOLUTION
I am having some "highlight" views in my design. And i have moved the relevant views behind those transparent background views by accident. So now i am not using addSubview: but insertSubview:atIndex: instead.
But marking #Anthonin C. answers as the right one. Because it pointed me in the correct direction (I found it out by overriding the hitTest: method)
Would you please track the otherView.bounds property to verify that your touch isn't out of bounds. I faced this issue recently and Apple documentation provide solution here : Delivering touch events to a view outside the bounds of its parent view
Sorry, I don't have enough reputation to comment your answer.

UIScrollView: View inside don't fit properly, weird behaviour, view snaps back on touch

I am having trouble with a UIScrollView... I have researched a lot and found cases that were similar yet the solutions never seemed to work so here's a little explanation (it's quite hard to explain)
I have a viewcontroller that contains a UIScrollView and I load a couple of view controllers inside that uiscrollview... I instatnite them and then I add them to the scrollView like this...
viewController1.view.frame = scrollView1.bounds
var frame0 = viewController1.view.frame
frame0.origin.x = 0
viewController1.view.frame = frame0
self.addChildViewController(viewController1)
self.scrollView1.addSubview(viewController1.view)
viewController1.didMoveToParentViewController(self)
viewController2.view.frame = scrollView1.bounds
var frame01 = viewController2.view.frame
frame01.origin.x = self.view.frame.size.width
viewController2.view.frame = frame01
self.addChildViewController(viewController2)
self.scrollView1.addSubview(viewController2.view)
viewController2.didMoveToParentViewController(self)
...
after that I configure the scroll view as follows
self.scrollView1.contentSize = CGSizeMake(self.view.frame.width * 5, self.view.frame.height)
scrollView1.delaysContentTouches = false
Times 5 because there are 5 ViewControllers
(The scroll view has paging enabled)
Now this method works quite well and all views are loaded inside the scroll view and I can swipe trough my views however here's the problem.
My first view is loaded fine, then when I swipe to the right I see that the second view is offset to the top and a part of it is hidden, then when I touch a button inside the view it suddenly snaps to the bottom in it's place... This is really annoying because it makes the user interface really irritating since a part of it is hidden...
I have made an illustration of it: http://imgur.com/xHRwXt4
I have tried a bunch of things like setting
self.automaticallyAdjustsScrollViewInsets = false
or
self.scrollView1.clipsToBounds = true
but still the same problem occurs. I also thaught it was my navigation bar since the view containing the scrollview is located inside a ViewController that has a navigation bar (from uinavigationcontroller)
So for testing purposes I tried it without a navigation controller and bar but still the same kind of problem occurred. It's really frustrating and I can't seem to find a solution.
However I do have to say when the scrollview was located inside the ViewController with navigationBar and I set
self.scrollView1.contentSize = CGSizeMake(self.view.frame.width * 5, self.view.frame.height - 66)
(Notice the -66)
Then the snapping problem didn't occur, however this wasn't the effect I was looking for since the scrollview needed to fill up the whole view, also the view inside the UIScrollView wasn't completely visible then so that wasn't a solution either.
I also taught it was maybe because my constraints were differently set up on one of the view controllers I loaded so I tried it with initiating the same type of view controller a few times still the same problem occurred...
(So I have tried a lot but no matter what I try to do it gets worse or another problem occurs for each view moves in either direction then I add self.automaticallyAdjustsScrollViewInsets = false but then the snapping returns etc. etc.)
Hopefully somebody has some suggestions to solve this
(If I you need some more illustrations etc. then I will provide these too.)
Thank You
From the looks of it, there are 2 things to try:
Some of child view controllers may contain scroll views and their automaticallyAdjustsScrollViewInsets = false needs to be set other than the parent view controller
A very helpful way to find out is to turn on view debugging. Run the project, then go to Xcode > View Debugging > Show View Frames. See if the child views of the child view controllers are misaligned along the top. If they are properly aligned (top all flushed), it means the problem lies in the individual child views.

Finding a CGRect in a complex view hierarchy

I am using a coachmarks/wizard framework called WSCoachMarksView that darkens your entire screen, and lets you create a highlighted area within a given CGRect to illuminate a call to action. I have a somewhat complicated hierarchy of views, and was having trouble properly identifying the CGRect I want to highlight with respect to the parent windows.
Here is my hierarchy. The goal is to find the CGRect for Day1, which is an imageview of about 50 pixels in height, and 25 pixels in width.
-TabViewController
-NavigationViewController
-HomeViewController : ViewController
-Container View (container within homeviewcontroller)
====================Everything below is within this container==============
-
HomeContainerViewController : ViewController (this view is loaded within the container)
-WeekViewController: ViewController
DayView (just a view I dragged onto WeekViewController to hold subviews)
Day1 (this is a rectangle imageview within the above DayView)
So there is a containerview within HomeViewController. Based on the above hierarchy, I've tried a multitude of permutations to correctly highlight Day1, but all have failed. I think my understanding of convertPoint is wrong and wanted to ask for some help in understanding which parameters to pass.
I read somewhere that you can only work off of the relationship between the parent/child but not a grandparent, etc?
Given this:
//assume tabViewController is the TabViewController above
//assume weekViewController is self.view, or WeekViewController
let day1Point = weekViewController.view.convertPoint(day1.frame.origin, fromView: tabViewController.view)
So unfortunately something like this just highlights the area on the top-left of the screen (my day 1 is actually near the middle of the screen).
What's the approach for convertPoint or convertRect? Is it always the leaf node in the hierarchy tree, and the "fromView" parameter is the top-most view (parent node)?
Thanks so much, any clarification / suggestions would be very much appreciated!
edit
so something like this gets me closer:
var myFrame: CGRect = dayView.convertRect(day1.frame, toView: self.view)
but it's still off slightly depending upon iPhone 5, 5s, 6, 6plus (only part of the image appears).
Note by OP:
Fogmeister suggested in the comments to use viewWillAppear, but i'm sure he meant viewDidAppear (which is actually what led me to solve this problem). other than that, everything he suggested was spot on (i.e. the correct usage of convertPoint/Rect below). Thanks so much for your help!
Well, the way convert works is that it will take the absolute point and convert it to different reference views.
So you should be able to do something like this...
From the WeekViewController you should be able to do...
self.view.convertPoint(day1.frame.origin, toView:homeViewController.view)
or...
self.view.convertRect(day1.frame, toView:homeViewController.view)
You will need a reference to the homeViewController to do this. I',m not certain what you're trying to do though. Where is your code that is highlighting the view? Is that code in homeViewController?

CALayer is not positioned correctly within the View

Context
I am using this simple library to make an intro/walkthrough for my app (TL;DR, horizontal paging view controllers). I currently have 3 pages setup.
In the 3rd walkthrough page, I have a custom CALayer that animates a circle in an endless loop. I'd like to add that layer to a UIView in order to lay it out the way I want in IB (via auto layout).
In viewDidLoad (for the 3rd page) I create the circle layer and set its frame to be the same as the view I positioned, assuming the circle would be in the same spot as the view:
for v:UIView in [view1!, view2!] {
var pulse = PulseLayer()
pulse.frame = v.frame
pulse.cornerRadius = v.frame.width / 2.0
pulse.masksToBounds = false
view.layer.insertSublayer(pulse, above: v.layer)
}
Problem
When I run the app in the iPhone 6 simulator, the CALayers show up OUTSIDE their UIViews (see below).
One thing I noticed immediately is the layers are not misplaced the same way--one is above its view, and the other is to the left. I am assuming this is related to the constraints on the views, but I cannot figure out how to fix it.
Equally baffling to me is that when run on the iPhone 5 simulator, the layers appear exactly as I expect them to (see below).
I feel like I am misunderstanding some of the concepts at work here. How can I get the positioning to act the same? (Like the iPhone5 gif.)
Or, is there a better way to do what I am trying to do?
viewDidLoad is too soon. Remember, viewDidLoad is way early; the view is not in the interface yet and nothing has its ultimate size/position. If you're going to add the layers to view rather than as sublayers of the little views, you will have to run your layer-creation code much later, in order to get the position right. viewDidAppear: or viewDidLayoutSubviews will be safe - but of course you must use a bool flag so you don't do it too many times.
Personally I don't see why you don't add the layers to the little views. So you would just set pulse.frame = v.bounds and add as a sublayer to v, not to your view. It solves the positioning and gets the relationships right. And doing it in viewDidLoad would work, because when the views move, the layers move with them.

UIView not redrawing, but elements become misplaced

I'm having an issue with my app's layout which is a bit tricky to explain. When the app first starts, this is what I'm showing:
After the user taps "Create Profile", I animate those buttons and show a registration form instead:
Needless to say, the buttons are now not in their "natural" position. Note, however, that the text fields are - that's where I have placed them in the storyboard, but when the view first loads I hide them. The animations are working great, but then I needed to scroll my view up when the user gives focus to a text field and the keyboard hides the field. The details of how to trigger the bug are a bit hard to explain, so I managed to boil it down to what seems to be a redraw event, except that it isn't... Let me try and explain that.
First of all, here's what happens when the keyboard is about to show:
- (void)keyboardWillShow:(NSNotification*)notification
{
CGRect frame = self.view.frame;
frame.size.height -= 1;
self.view.frame = frame;
}
Notice that this is a test only, probably the minimal I found that would still trigger the bug. All it does is resize the view. I would expect the view to be exactly as it was, with one less pixel, right? Wrong. Here's what I get:
That is, all elements returned to their "natural" positions, completely ignoring their previous positions. My first guess was that it would seem that the window is redrawing, so I tried this:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
NSLog(#"View was drawn");
}
But this only triggers when the window is first drawn, not when this strange behaviour happens. To understand what I mean by "natural position", here's what I have in storyboard:
You can also see that I'm not using constraints and the underlying structure of my view:
The full code for the entire setup is quite extensive, so pretty much not practical at all to show. However, how I animate the subviews resumes to changing their frame as I did in keyboardWillShow, and setting their positions to whatever I need.
Any ideas?
So you're using storyboards and you have "Use AutoLayout" set to false for your entire storyboard?
In that case your app is using "struts and springs" style placement rules. You're going to have to debug those.
It's a bit hard to describe everything in a view controller in a post. It's easier to go over it in IB. Perhaps you can write a utility function that logs all the autoresizingMask values for the views in your view controller, and go over those, and perhaps post them here describing the autoresizingMask values for each view in your original post.

Resources