UIDynamicAnimator - Can I use the main UIWindow as the reference view? - ios

I'm working on an app that uses a UITabBarController, where each tab contains a UINavigationController stack.
I'm attempting to create a UIDynamicAnimator that will use UIDynamicBehaviors to animate in, from the top, a UIView from under the UINavigationBar, such that it collides with the UITabBarController's UITabBar and pushes it off the screen.
In order to achieve this, my reference view for the UIDynamicAnimator must contain the UITabBarController's view.
Is it okay to use the UIWindow instance as the reference view for the UIDynamicAnimator?
(Please provide feedback on this approach as well, I see others modifying the frame of UITabBarController.tabBar - is that bad practice?)

Any view will do. All you are doing is setting the frame of reference.
Be aware, however, that the window does not rotate when the device rotates (window coordinates are screen coordinates, and are fixed with respect to the device). Thus, working with its coordinates can be a nightmare.
Given the nature of your question, since everything is happening inside the tab bar controller, I don't see why you don't use the tab bar controller's view. It contains the tab bar and the various views of the child view controllers. If it is the root view controller (as I suspect it is), it contains absolutely everything else, and it itself is not going anywhere.

Related

Are UINavigationControllers equivalent to having manually wiring each VC with a navbar?

If I have the storyboard in the form of (where the arrows are segues)
UINavigationController -> ViewControllerA -> ViewControllerB
Would that be basically more or less equivalent to
ViewControllerA -> ViewControllerB
(NavigationBar) (NavigationBar)
if I manually wire each NavigationBar up to Button Bar Items with event listeners attached to unwind segues?
Or does UINavigationController offer something more than that?
UINavigationController is what is known as a container view-controller: it takes a bunch of other view-controllers and manages how their views are presented on the screen. UISplitViewController is another example of a container view-controller.
In the case of UINC, it:
Allows pushing a new "top" controller, animating it with a left-to-right/right-to-left animation depending on locale
Remembers the stack of previous top controllers, allowing you to pop back to them
Adds a UINavigationBar view above the top controller's view so the user can pop back by themselves (you can disable this)
Sets a layoutMargin on the top controller's view, so it can adjust content to not underlap the bar
Provides an edge-swipe gesture so the user can interactively pop to previous controllers (slowly peel the top sheet back)
For more information, including about how to create your own container view-controllers, see Apple's documentation on the subject: https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
There are some differences.
One that I have noticed, a UINavigationController will handle correctly putting the navigation bar in the right place for iPhone X vs other models (It will make the height larger so it goes into the wedge of the of screen, while just a nav bar will remain its standard height).
If you just put a nav bar on each UIViewController, you are going to have to check on each view controller if you need to update the bar size.
Basically the navigationController provides you many thing that you might use: A stack of UIViewControllers, a navigation bar, a toolbar, delegate methods, show/pop animations, etc. Doing all that by hand will be not too appropriate and a little bit messy. I suggest you to take a look to the Appleā€™s documentation for UINavigationController. There you will get a much better understanding of all the capabilities and methods that this class provides you.

Dynamically increase/decrease height of custom UINavigationBar

I followed this blog post that explains how you can implement a custom UINavigationBar that has an increased height, if for example you wanted to put additional ui elements in the nav bar underneath the rest of the bar content that will persist between navigation on the stack. This code works really well in the case where you always want it to be that increased height.
In my app, I need to start the navigation bar at its default height, then increase it later, adding more content, after the user performs a given action. Very similar to the song info and controls in the iTunes Store:
So I put some checks in place to not reposition anything if a BOOL property is NO. When I set it to YES, I call [self setNeedsDisplay] which will call layoutSubviews to position everything appropriately based on that boolean value. sizeThatFits is also called and I return the proper height.
The problem is, I can't call [self setTransform:CGAffineTransformMakeTranslation(0, -(NavigationBarHeightIncrease))]; in initialize. Instead I call that at the same time I change the boolean value to YES. Because of this, all of my elements are moved up that amount. But if I don't call setTransform, the elements in the nav bar are in the proper position, but the bar itself is positioned too far down, so that the custom view I've added to the bar is shown overtop the view controller's view - it bleeds out, and the extra space I added is black not the navigation bar's background color.
If I call setTransform in initialize, when the height is the default height, the elements are moved up when they shouldn't be.
So, how can I properly dynamically change the height and positioning of a UINavigationBar subclass?
As suggested in the comments, to achieve the behavior where a custom navigation bar (not subclassing the native control) persists across pushes and pops of controllers in a navigation controller, you'll need to have a single controller with the custom navigation bar and then a single embedded view that resolves to a UINavigationController with its view controllers underneath. Then, it will also be necessary to set the navigation controller's delegate to the root controller so that the title and other properties can be updated as the sub-controllers are pushed and popped. I've provided a screenshot below of what the storyboard version of this might look like:
An option is to create a UIViewController in storyboard which has only the control view you wish to show below the navigation bar with everything else transparent. The advantage here is you design this using the normal tools. Use constraints to place it just below the navigation bar and to set the heights, widths etc of your views.
When you wish to show the control, you can create an instance of this UIViewController and remove the content view from it and add it to the view hierarchy of what is on screen.
There are two options for inserting the extracted base view:
If you add this control view to the view controller at the top of
the navigation stack (what is on screen), it would be covered when
you push on a new controller. This is not what you said you wanted.
If you add this control view to self.navigationController.view, then
it will persist across pushes and pops. This is what you said you wanted.
I use this approach to provide popup help bubbles to describe what is on screen. Depending on whether I use option 1 or 2, I can persist help across multiple pushes/pops.
I got the idea from this tutorial which describes the general approach: http://blog.typpz.com/2013/12/09/ios-sdk-create-a-pop-up-window/
That link provides a full code example on how to bring up the view and remove the view.
This would let you design it in IB, present and dismiss it as required and persist it across navigation sequences.
Hope this helps.

How do I show a view over the tab bar but not over the navigation bar?

I want to present a modal view that shows over the UITabBar but not over the UINavigationBar.
From what I understand the best way to overlay a view on top of everything is to position it in its own UIWindow. This works great, and if I make the y origin of the view in the window lower than the navigation bar it works perfectly.
However, getting it lower than that y origin is really hard. I can use the topLayoutGuide.length property to position the y, but when I rotate that no longer is true as the navigation bar height changes.
How should I be doing this?
I had to deal with something similar recently, and ultimately to modally present a new view you'll be looking to call the navigation controller and then set its setDefinesPresentationContext: to Yes.
setDefinesPresentationContext: is needed so that a presenting view controller will restrict the bounds of a presented view to the visible area of the presenting view. As a result, the view controller is kept within the bounds of the currently visible window (The default is no, in which case the presented view keeps asking for a defining context up through the VC hierarchy until it reaches a VC that claims this role, or until it hits the UIWindow).
With this method, you won't have to calculate the bounds of your view, it'll just be done automagically. Check out the Apple docs on this method for the full explanation though.
(and shameless plug, I wrote about it on my blog. it's under "Presenting the Search Bar".. pardon the formatting, still a work in progress)

How do I make a now playing bar like in media player apps in iOS with XCode?

I am making a media player app. I have UITableViewControllers that are embedded in Navigation Controller. I would like to somehow make a view that would overlay potentially multiple (2?) of these tableviewcontrollers (one that shows the user's playlists, and the next would show the tracks in the playlist), but only at the bottom, like a now playing bar in (e.g.) Spotify's iOS app (as in the left bottom side of this
or as in this
I have tried to drag a Container View into my Navigation controller (in which my TableViewCell is embedded), but it won't let me drop it there.
It looks like I can drag the Container View into my TableView, but then how would it remain there when i navigate between tableviews?
Trying to follow #Rintaro's suggestion, but I'm a little new to XCode. How did you do this? So I made a single view application, i added a container view to the first VC, it's imm drawing it somewhere else in the storyboard, but i can't figure out how to tell that view that it's a navigation controller. also, as soon as i add a second container to the first VC and tried to size it, the first container disappears! It is still listed in the hierarchy on the left, and still has an arrow pointing out of it, but the view controller that was added and was being pointed to is also invisible?!
UPDATE: This works very well, but it has a problem with orientation changes. how can i make this work in both orientations? (I hypothesize that it currently positions the "nowplaying view" off screen when the orientation changes).
Basically, view hierarchy like that is constructed like this:
Using two "Container View" in initial view controller, one for Navigation Controller, one for "Now Playing Bar" view controller.
ADDED:
"Main Container View Controller" would be your own UIViewController subclass. It will have responsibility to show/hide "Now Playing Bar".
Workaround for Interface Builder strange behaviors.
You can set Auto Layout constraints like following. Maybe you might want to select the view from the left menu.
Note that, you should uncheck Constrain to margins check box.
Container View for Navigation Controller:
Container View for Now Playing Bar Controller:
And then, Update Frames from the menu:
If you are manually placing any buttons with absolute coordinates, make sure that you update the coordinates of them when the rotation is changed.
Obviously you need to create a custom UIView class, where you will show this menu. And when you will create it, add it to your view just like here:
float y = ROOTVC.view.frame.size.height - 49;
[self setFrame:CGRectMake(0, y, 320, 49)];
[ROOTVC.view addSubview:self];
[ROOTVC.view bringSubviewToFront:self];
I would simply add a nowPlayingView to the appDelegate.window and adjust either:
The window.rootController.view frame to avoid overlapping the nowPlayingView.
If window.rootController.view is a UIScrollView, adjust its contentInset.bottom to avoid overlapping the nowPlayingView. You can also use transparency/translucency with this solution.
Add a dummy toolbar to the controller that will get covered by the nowPlayingView.
If your window.rootController is a UINavigationController, then you have to probably do the above fore each controller you push. The nowPlayingView will stay unchanged on top.

Why shouldn't I modify the UINavigationController's toolbar?

The documentation for the toolbar property in UINavigationController says:
This property contains a reference to the built-in toolbar managed by the navigation controller. Access to this toolbar is provided solely for clients that want to present an action sheet from the toolbar. You should not modify the UIToolbar object directly.
I can understand why I shouldn't modify the toolbar's visibility or items, because UINavigationController provides an interface to do that. But I've added a button that, when tapped, causes the toolbar to animate offscreen. Why shouldn't I do this?
Must I instead create my own ToolbarNavigationController class that replicates everything UINavigationController does with the toolbar just sod, I can do what I want with the toolbar? Seems like a waste of effort when the UINavigationController already does what I want. Why on earth would the docs suggest I so severely limit what I do with it?
Three ways that having moved the navigation bar might confuse it:
If your app can be rotated, does it stay in the right place after rotation? When it returns to the screen, does it animate on from the right place?
If you display a search bar, the navigation bar animates itself off. If it's already been moved manually, does it know where it is?
If you push a viewController with hidesBottomBarWhenPushed set to YES, and return, does the navigationController put its toolbar back where it belongs?
On the other hand, how about calling UINavigationController setNavigationBarHidden:NO animated:YES - does that do what you want, while letting the navigation controller maintain control of its toolbar?
Edit: Sorry about misreading. Yes, you probably can do what you're asking, as long as you don't also do anything (like item 3 above) that mean the navigationController moves its toolbar around.
On the other hand, the behavior you want can also be achieved as follows:
For the viewController with the multiple toolbars, set its hidesBottomBarWhenPushed to YES.
Place all the toolbars that viewController needs on it, and have it take full control of their positions and visibility.
If the default toolbar has the same layout as the navigationController's own toolbar, this will create the odd visual effect of seeing the same set of toolbar items slide off and then on again, but everything else should work.
UIToolbar is inaccessible because it doesn't need to be accessible. It responds to +appearance just fine. In your case, you can access the properties of a readonly variable (look at CGRect!). If you need to animate a UIToolbar or UINavigatiomBar offscreen, set it's frame.center property equal to a CGPointMake in a UIView animation block.

Resources