Sequential timings without Core Animation - objective-c

My current iPad project-in-progress has the need for a sequence of events; It roughly reads like this:
→ User taps a button in a floating panel on top of a scroll view
→ floating panel animates to close
→ model reloads some data
→ scroll view displays new data
→ scroll view scrolls to a new content offset
→ new floating panel opens
This is not just a simple animation. Halfway through the sequence it needs to pause, the model needs to do some background processing and then report back when done, after which the sequence can be resumed. Also, the animation of the floating panels is achieved by using the convenient UIView animateWithDuration:animations:completion: method.
When I search stackoverflow and the rest of the web for sequencing solutions, the only thing that pops up is Core Animation. Yet this is not a case for CA.
What would be the best way to go about sequencing and timing method calls like this?
Cheers, EP.

CoreAnimation is a candidate, and is what is used internally by the UIView convenience class methods. As you say, it's not a complete solution, but is definitely worth keeping for the panel animations.
Otherwise, I'd imagine the overview would be:
User taps a button in a floating panel on top of a scroll view; view controller issues (i) a request for the animation on the floating panel; (ii) that the model begin to load data (I'll assume asynchronously)
At some point CoreAnimation reports that the animation is done. At some point the model reports that the new data has been loaded. The view controller waits until both things have occurred.
New data is pushed to the scroll view
The view controller scrolls the view, probably using either -scrollRectToVisible:animated: or -setContentOffset:animated: (I'll assume with animation set to 'YES')
the view controller also being the UIScrollViewDelegate, it waits to receive -scrollViewWillBeginDecelerating: or -scrollViewDidEndDecelerating:, or possibly checks the contentOffset in -scrollViewDidScroll:
at the right moment it then has CoreAnimation transition in the new floating panel.
So you're allowing the scroll view to handle its own animation and trusting it to report animation status, using CoreAnimation as your code already does for the panel and otherwise wiring everything together in the view controller. There's no need to use CoreAnimation explicitly, just stick with the UIView methods you're already using.
If your model is synchronous rather than asynchronous then just start the processing immediately after issuing the request to get rid of the first floating panel — CoreAnimation tasks continue even if the main thread is busy.

Related

How do I efficiently manage screen size changes and redrawing with storyboards, initWithCoder, viewDidLoad and viewDidLayoutSubviews?

I am using storyboard layouts to set up view layout.
I am supporting both iPhone and iPad layouts.
When the view is created with initWithCoder:, it is initially created with the frame size of the device I was last looking at in Interface Builder.
If I am designing with the iPhone X layout in interface builder and then build and run on an iPad, the view is initially created with iPhone X screen dimensions. Then viewDidLayoutSubviews: is called and it updates the screen dimensions to the correct iPad size.
The subviews are using drawRect inside UIViews to draw the view graphics. I am doing this so I can change graphic colors via code. I change the color variable and then call setNeedsDispay on the view to redraw the view with new colors using CGGraphicsContext commands.
It also allows me to draw any graphic image at any size. And with lots of graphics that means I don't have to include all the different images at 1x, 2x and 3x sizes in my bundle. It's all drawn dynamically.
Some of these images are laid out when the view loads and not in Interface Builder. So I check the screen size and draw the button size and position accordingly.
What happens is, viewDidLoad is called and it draws the graphics based on the initial screen size.
Then viewDidLayoutSubviews is called and I have to update the drawing of the subviews I placed manually repositioning them based on new screen dimensions and then calling the drawRect on them. I feel like this is just unnecessary extra work for the device.
In addition to that, viewDidLayoutSubviews is called for other reasons then just resizing the view on initial load of the viewController. So then each time it's called it will go redraw the subviews, even if they don't need it.
And, if the device I am running on is the same as the device I was using in Interface Builder, it doesn't call the viewDidLayoutSubviews. I can't just let the view layout the subviews there because there is no guarantee it will be called.
My solution so far is creating a variable to track the screen width. I set the variable in viewDidLoad. If it creates the view at iPhone X size, my screenWidthTracker = 1125. When viewDidLayoutSubviews is called I compare the current screen size to screenWidthTracker.
if (self.view.frame != screenWidthTracker) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateView" object:self];
};
if the view has changed size, it sends a message to redraw views. Any views I have placed manually as subviews are registered to listen for #"updateView".
Is there a better way to manage this? Is there a method that gets called ONLY when screen dimensions change and not when its updating the position of other subviews? Should I be utilizing viewDidAppear? I feel as though that is too late in the chain and I don't want the user to see button size updates.
In general, from the description you've given, I'd say that at the very most, you should be doing as much size work as possibly by using autolayout (which you can configure entirely within the storyboard) and then implementing only drawRect and viewDidLayoutSubviews — and the latter only if you need to.
A very common strategy is to use a boolean flag to set up initial conditions in the first call to viewDidLayoutSubviews, and not use it thereafter. Screen size changes after launch (not at launch), such as rotation, are detected by implementing willTransition(to:with:) — and even then you should check to see that the old size and new size are not the same (180 degree rotation) and do nothing if they are.

How can I set the end dragging velocity of a UICollectionView with custom flow layout?

I'm working on a UICollectionView with a custom flow layout subclass which, among other things, does some custom "paging". Everything's fine but for the fact that depending on how I drag, when I release and after - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity gets called, the collection view (or some part of the UICollectionViewFlowLayout which I did not yet know I need to override) is controlling the velocity with which the animation of an item snapping happens.
That is, if I slightly offset an item from the center of the
collection view qnd release, it snaps back to its position pretty
quickly (desired).
But If I drag the item, say, half way past the collection view's
frame and/or change swiping directions while still dragging and then
release, the "snap" animation takes too long (not desired: I'd like the velocity to adjust so that the end drag animation takes the same amount of time always, regardless of distance).
I tried modifying the decelerationRate of the collection view but it doesn't seem to do anything. And I'm thinking of writing my own animation block in one of the collection view delegate methods, but I'm wondering if there is a different way (perhaps from within the flow layout subclass?).
Well, actually setting self.collectionView.decelerationRate = 0.; seems to work for now. It at least does not decelerate the scrolling and so it looks like constant velocity which is not exactly what I wanted but feels almost right.

When using UIScrollView, how do I make the viewed ends to be complete, not partial

How do I make the UIScroll view show complete views, not partial views?
(Note) I don't want it jumping to a complete view. It needs to move naturally or at least not immediate... needs to be smooth.
thanks
If your views are all of a constant size and you just want left/right or up/down scrolling, set pagingEnabled on the scroll view to YES. Supposing you wanted your scroll view to be 320x480 but to show the sides of the next and previous pages (so, e.g., each thing inside the view was 280 points wide), you'd size the scroll view to be 280x480 but set masksToBounds to NO.
If you have a more complicated scheme, install a scroll view delegate and act on scrollViewDidScroll:, paying attention to contentOffset. Probably you want to implement logic like:
add an observer on tracking; when it transitions to NO from 'YES' enable your logic inside scrollViewDidScroll: In there:
if a forced scroll is pending, cancel it
calculate where you'd force the scrolling to from the current position
schedule a scroll to there (which you'll effect via setContentOffset:animated:) for half a second from now
You can use a non-repeating NSTimer for the scheduling aspect. The logic you've essentially implemented is that if the user stops adjusting the view, wait for the natural inertia to end (which you'll detect by the 0.5 second gap since last movement), then transition smoothly to the nearest aligned position.
Check out Apple's documentation here first:
https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/UIScrollView_pg/Introduction/Introduction.html
Then the ScrollViewSuite sample:
https://developer.apple.com/library/ios/#samplecode/ScrollViewSuite/Introduction/Intro.html#//apple_ref/doc/uid/DTS40008904
I think you are referring to Paging techniques.

How to create an "Add to reading list" animation effect

I'm trying to make an animation effect similar to the one on Safari(iPhone) when you add an element to the reading list. It's similar to the one that appears when starting to download an item from App Store application: the application item drops to the dock to start downloading.
First it bounces up and then goes to the dock. It's a very nice effect that Apple uses on their OS.
I have an image view on screen that I want to drop with this kind of animation to my toolbar in my application.
If there is someone who did it or know what's the name of the effect, could please tell me how to do it.
Thank you.
"Add to reading list" shows no animation on my phone but of your description it sounds like the "Open in background"-animation in Safari (iPhone). My answer describes that animation.
I wrote a thing like that a few months ago and much of it is doable while some of it is not. Your questions showed me that more people are to know how it is done so I wrote a blog post about it. I will describe the high level approach and challenges here but you can read more about it in that post.
Getting to content to animate
If you choose to animate the view that is on screen down to the (in your case) tool bar then you will only have to access its layer. If you want the original view to remain and animate a visual copy (like the "open in background"-Safari animation) down to the bar item then you should create a new layer and draw the content of your layer into an image and set that image as the content of the layer that you are animating
Calculating the end position
The start position of the animation is simply the frame of the view. The end position is very tricky since bar items (both tool bar items and tab bar items) are not UIView subclasses and doesn't have a public view property. This causes problems when you want to shake the bar item later on.
I decided to make a visual approximation of the end position using some simple heuristics. If you know before hand that you will only animate to a single bar item then the end position can be hard coded to a suitable frame.
Animating along a path
There is nothing special to moving, scaling and rotating the layer from the start to the end position. If you want to read more about how I did it you can look at the post I wrote.
Shaking the bar item
This cannot be done without a lot of custom code or using private API at the moment. Since bar items doesn't have a view or a layer there is no accessible layer for you to animate. I guess that you could have a custom animating image that does the shake and set that during the animation and set the new image afterwards. The approach of drawing into an image and animating that doesn't work that well either since there is no accessible layer who can draw its content into the image (you want this for the special effect of the tool bar item and tab bar item).
...put all this together and tweak it to your special needs and you will have an animation that resembles the animation you are looking for.

IKImageView zooming controlled by an NSSlider

What's the best practice for setting zoom factor of an image within IKImageView via NSSlider?
I was able to bind a slider either to zoom in OR zoom out action of an IKImageView.
Obviously, what I'd rather see is a single slider controlling both those actions.
Best, if image is refreshed after each change of the slider (continuously, even if a mouse button is not released yet).
This demo explains a lot: ImageKitDemo
In particular, this snippet is what I was looking for:
- (IBAction) zoomSliderDidChange:(id)sender
{
[addProductPhotoImageView setZoomFactor:[sender floatValue]];
}
The Bindings way would be to bind both the Zoom Factor of the IK image view and the Value of the slider to the same property of your controller. When the slider changes the value of the property, the image view will be notified, and will go get the new value from your controller.
One advantage of this way is that you can add more ways of zooming in and out and the value in the slider won't go stale. For one example, if IKImageView adds pinch-zooming (or if it has it already—I don't have multi-touch on my Mac), the user can zoom that way and the slider will update automatically. That won't happen with the IBAction solution.
Another example would be Zoom In and Zoom Out menu commands (perhaps with ⌘+ and ⌘- keyboard shortcuts) that send action messages to your controller. Your controller would respond by increasing or decreasing the value of the property (using a setter method it implements). With Bindings, both the image view and the slider will update for free. Without Bindings, you would have to explicitly talk to both the image view and the slider, telling one to update its zoom factor and the other to update its slider.
A third example would be a “Zoom factor: X%” display in a corner of your window. With Bindings, this can update for free no matter how the user zooms the image: moving the slider thumb, pinching/unpinching the image, or pressing a menu item. Without Bindings, this would be yet another thing you have to talk to in your (at least three) change-the-zoom-value action methods.