Finding a CGRect in a complex view hierarchy - ios

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?

Related

Embedding Intrinsically Sized View Breaks Animation

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.

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.

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.

Get width and height of container from view

Forgive me if this is a duplicate. I thought for sure this would be an obvious question.
I have a container in a storyboard, which is of class ModelViewController (the whole view controller). From ModelViewController I need to get the width and height of the container. I am currently trying:
self.view.bounds.size
Which yields 0.563380 and 0.000000. I also tried
self.view.frame.size
Which yields the dimensions of the entire screen. What am I missing here?
From this post
View frames are not actually useable in viewDidLoad; you should move all of your geometry-manipulating code into viewWillAppear. The system will clobber any changes you set in viewDidLoad between there and when it's about tom come on screen.
And the comment
Actually, you should move geometry changes to -viewDidAppear pre-iOS 5 and to -viewWillLayoutSubviews for iOS 5+. -viewWillAppear: may not actually have the correct bounds/orientation if the view is being animated.
I found the solution. The correct dimensions are indeed given when called from viewWillLayoutSubviews. I was calling from viewDidLoad giving incorrect results.

Resources