Showing posts with label uiviewanimationtransition. Show all posts
Showing posts with label uiviewanimationtransition. Show all posts

Saturday, April 16, 2016

Understanding the value of UIViewController transitions

Leave a Comment

I am trying to learn some new iOS programming patterns. I have read a bunch about the UIViewController transitioning APIs added in iOS 7. They look cool but also feel pretty heavy for what seems like a simpler task.

Consider this use case: I have a custom container view controller that manages "slides". It holds an array of slide view controllers and the user can move forward and backward though them by tapping a button.

I can implement the transition for this as follows:

private func transitionToViewController(viewController: UIViewController, direction: TransitionDirection = .Forward, animated: Bool = true) {     currentViewController.willMove(toParentViewController: nil)     addChildViewController(viewController)     // ... set up frames, other animation prep ...     contentContainerView.addSubview(comingView)     UIView.animate(duration: 0.5, animations: {          // do the animations     }) { (finished) in         leavingView.removeFromSuperview()         self.currentViewController.removeFromParentViewController()         viewController.didMove(toParentViewController: self)         // final clean up     }  } 

How would the newer transitioning APIs improve this? From what I understand, these APIs are even more complicated to use if you are rolling your own container view controllers (see custom-container view controller transitions.

Is the value in the transitioning APIs mostly for interactive transitions?

Thanks for clarifying

2 Answers

Answers 1

I think the new transitioning API (UIViewControllerTransitioningDelegate and friends) is simply a final step in the generalization of view transitions between controllers.

In first versions of UIKit we had to hack the system transition code to get any custom transitions at all. Years later we got controller containment that made it possible to manage view controllers as first-class citizens and create our own interactive transitions. The final step is having a full-featured general system API for any transition that you can dream of – that’s the new transition API.

The new API makes it possible to extract the transitions into standalone classes. Which, in turn, makes it finally possible to just download a transition library off GitHub and plug it into your existing code as a simple transition delegate. No need to derive your view controllers from some particular superclass, no need to use a third-party controller container, no need to add extensions to UIKit classes. Now the transitions are finally first-class citizens in UIKit, too.

Answers 2

How would the newer transitioning APIs improve this?

TL;DR Maintainability via Encapsulation, Reusability, Testability.

Encapsulation: The comments indicate that there's logic to set up and state to track with the animation. Your view controller is probably plenty big enough already; putting transition logic somewhere else makes each piece smaller and therefore more maintainable.

Reusability: What's the next thing you'll do after this? Set up the transition back from the transitioned to controller, no doubt. And is it likely to be a reversal of this animation? Pretty likely. So you'll copy and paste this code to that controller and reverse it there, probably. Now you've got two copies. One copy using the transitions would be more maintainable. (Also note the existence of pods of custom transitions, as reusability and shareability go hand in hand.)

Testability: Code embedded in a heavyweight view controller is notoriously difficult to test. A custom transition can be tested in isolation without the state overhead of the live views.

So for any code you intend to look at more than once, the transitioning APIs are probably worth the effort!

Read More

Thursday, April 14, 2016

UIView.animateWithDuration Not Animating Swift (again)

Leave a Comment

Note: I’ve already checked the following stack overflow issues:

27907570, 32229252, 26118141, 31604300

All I am trying to do is fade animate in a view (by alpha) when called by an IBAction attached to a button. Then reverse when a button on the view is hit.

My wrinkle may be that I'm using a secondary view that is on the ViewDock in the storyboard View. The view is added to the subview at the time of viewDidLoad where the frame/bounds are set to the same as the superview (for a full layover)

The reason this is done as an overlay view since it is a tutorial indicator.

The result (like many others who've listed this problem) is that the view (and contained controls) simply appears instantly and disappears as instantly. No fade.

I have tried animationWithDuration with delay, with and without completion, with transition, and even started with the old UIView.beginAnimations.

Nothing is working. Suggestions warmly welcomed.

The code is about as straight forward as I can make it:
Edit: Expanded the code to everything relevant
Edit2: TL;DR Everything works with the exception of UIViewAnimateWithDuration which seems to ignore the block and duration and just run the code inline as an immediate UI change. Solving this gets the bounty

@IBOutlet var infoDetailView: UIView! // Connected to the view in the SceneDock  override func viewDidLoad() {     super.viewDidLoad()      // Cut other vDL code that isn't relevant      setupInfoView() }  func setupInfoView() {     infoDetailView.alpha = 0.0     view.addSubview(infoDetailView)     updateInfoViewRect(infoDetailView.superview!.bounds.size) }  func updateInfoViewRect(size:CGSize) {     let viewRect = CGRect(origin: CGPointZero, size: size)      infoDetailView.frame = viewRect     infoDetailView.bounds = viewRect      infoDetailView.layoutIfNeeded()     infoDetailView.setNeedsDisplay() }  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {     super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)     updateInfoViewRect(size) }  func hideInfoView() {     AFLog.enter(thisClass)     UIView.animateWithDuration(         2.0,         animations:         {             self.infoDetailView.alpha = 0.0         },         completion:         { (finished) in             return true         }     )     AFLog.exit(thisClass) }  func showInfoView() {     AFLog.enter(thisClass)     UIView.animateWithDuration(         2.0,         animations:         {             self.infoDetailView.alpha = 0.75         },         completion:         { (finished) in             return true         }     )     AFLog.exit(thisClass) }  // MARK: - IBActions  @IBAction func openInfoView(sender: UIButton) {     showInfoView() }  @IBAction func closeInfoView(sender: UIButton) {     hideInfoView() } 

Please note, I started with the following:

func showInfoView() {     UIView.animateWithDuration(2.0, animations: { () -> Void in         self.infoDetailView.alpha = 0.75     }) }  func hideInfoView() {     UIView.animateWithDuration(2.0, animations: { () -> Void in         self.infoDetailView.alpha = 0.00     }) } 

7 Answers

Answers 1

There are several strange things I can see,

first, remove:

infoDetailView.layoutIfNeeded() infoDetailView.setNeedsDisplay() 

Usually you don't need to call those methods manually unless you know exactly what you are doing.

Also, when you are changing the size:

infoDetailView.frame = viewRect infoDetailView.bounds = viewRect 

You never need to set both bounds and frame. Just set frame.

Also, you should probably make sure that the view actually doesn't ignore the frame by setting:

infoDetailView.translatesAutoresizingMaskIntoConstraints = true 

Instead of resetting the frame, just set autoresize mask:

infoDetailView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] 

Resulting in:

override func viewDidLoad() {     super.viewDidLoad()      // Cut other vDL code that isn't relevant      setupInfoView() }  func setupInfoView() {     infoDetailView.alpha = 0.0     infoDetailView.translatesAutoresizingMaskIntoConstraints = true     infoDetailView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]     infoDetailView.frame = view.bounds     view.addSubview(infoDetailView) }  func hideInfoView() {   ... } 

I think this should actually help because immediate animations are often connected to size problems.

If the problem persists, you should check whether the infoDetailView in your animation is the same object as the infoDetailView you are adding to the controller.

Answers 2

Use this code:

UIView.animateWithDuration(0.3, animations: { () -> Void in     self.infoDetailView.alpha = 0.0  }) 

Answers 3

Make sure infoDetailView's opaque is false.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instp/UIView/opaque

This property provides a hint to the drawing system as to how it should treat the view. If set to true, the drawing system treats the view as fully opaque, which allows the drawing system to optimize some drawing operations and improve performance. If set to false, the drawing system composites the view normally with other content. The default value of this property is true.

Answers 4

Try Below code. Just play with alpha and duration time to perfect it.

Hide func

func hideInfoView() { AFLog.enter(thisClass) UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.8     },     completion:     { (finished) in         UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.4     },     completion:     { (finished) in         self.infoDetailView.alpha = 0.0     } )     } ) AFLog.exit(thisClass) } 

Show func

func showInfoView() { AFLog.enter(thisClass) UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.3     },     completion:     { (finished) in         UIView.animateWithDuration(     2.0,     animations:     {         self.infoDetailView.alpha = 0.7     },     completion:     { (finished) in         self.infoDetailView.alpha = 1.0     } )     } ) AFLog.exit(thisClass) } 

Answers 5

If the animation does not seem to execute then consider examining the state of each of your views, before you enter the animation block. For example, if the alpha is already set to 0.4 then the animation that adjusts your view alpha, will complete almost instantly, with no apparent effect.

Consider using a keyframe animation instead. This is what a shake animation in objective c looks like.

+(CAKeyframeAnimation*)shakeAnimation {     CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];     animation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-10.0, 0.0, 0.0)],                          [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(10.0, 0.0, 0.0)]];     animation.autoreverses = YES;     animation.repeatCount = 2;     animation.duration = 0.07;     return animation; } 

Here is a post that shows you how to adjust alpha with keyframes http://stackoverflow.com/a/18658081/1951992

Answers 6

I've replicated your code and it work well, it's all ok. Probably you must control constraints, IBOutlet and IBActions connections. Try to isolate this code into a new project if it's necessary.

Update: my code and my storyboard and project folder photo:

my storyboard and project folder photo

Every object (view and buttons) are with default settings.

enter image description here

I've commented all AFLog lines (probably it's only any more "verbose mode" to help you) , the rest of your code is ok and it do what do you aspected from it, if you press open button the view fade in, and when you tap close button the view fade out.

PS Not relevant but i'm using xCode 7.3 , a new swift 2.2 project.

Answers 7

If you infoDetailView is under auto layout constraints you need to call layoutIfNeeded on the parent view inside animateWithDuration:

func showInfoView() {     self.view.layoutIfNeeded() // call it also here to finish pending layout operations     UIView.animateWithDuration(2.0, animations: { () -> Void in         self.infoDetailView.alpha = 0.75         self.view.layoutIfNeeded()     }) } 

Theoretically this should not be needed if you just change the .alpha value, but maybe this could be the problem in this case.

Read More

Preserve custom tabbar view state between view controllers

Leave a Comment

We have a custom view, which looks like a tabbar but is ultimately a subclass of UIView.

The view is shown as a tabbar would at the bottom of a UIViewController. When an image is touched in the view controller we transition to another view controller.

The second view controller has the same fake tabbar view being shown at the bottom. The user can close the second view controller and it will transition back to the first.

What is the best way to keep the same view and its state for both view controllers? For example part of the fake tabbar might be a usable button with a badge icon showing (2). If that is touched it would go down to (1). This would need to be reflected on both instances of the view.

Would the correct approach be to just use prepareForSegue as normal and keep updating the view state or passing the views instance around? Or is there a better approach?

4 Answers

Answers 1

I think the best approach is to implement something similar with the native tab bar. You can achieve this by implementing a container view . How you do that is a long story to post here but there are many resources on the internet. Basically you will have the same fake bar and your view controller will be shown in container view that should be put just above the tab bar. The view controller with both the container view and the tab bar should manage the transitions and update the bar.

Answers 2

Yeah, just as Jelly said I'd go the parent/child view controller route, with the 'tab bar' managing adding/removing the view controllers and associated views in response to touch events.

Answers 3

I am working on the same scenerio. In a UIViewController take your tabBar view at the bottom and above that take a blank UIView. Now on click of tabBar button, add and remove your new ViewController's view using AutoLayout like as -

#pragma mark - TAB BAR METHODS  -(void)setSelecedView:(VIEWSELECTION)selecedView {     [self RemoveChildViewControllers ];     switch (selecedView)     {         case VIEWSELECTION_HOME:         {             HomeViewController *homeVC = [[HomeViewController alloc]initWithNibName:@"HomeViewController" bundle:nil];             self.titleString=@"Wellborn Company App";             [self displayContentController:homeVC OnView:self.DumpingView];         }             break;         case VIEWSELECTION_SEARCH:         {             SearchViewController *searchVC = [[SearchViewController alloc]initWithNibName:@"SearchViewController" bundle:nil];             self.titleString=@"Search";             [self displayContentController:searchVC  OnView:self.DumpingView];         }             break; }}  #pragma mark - VC Adding/Removing Methods  - (void)RemoveChildViewControllers {     NSArray *childVCArray = [self childViewControllers];      for ( __strong UIViewController *childvc in childVCArray)     {         [childvc willMoveToParentViewController:nil];         [childvc.view removeFromSuperview];         [childvc removeFromParentViewController];     } }  - (void)displayContentController:(UIViewController*) content OnView:(UIView*)parentView {     [self addChildViewController:content];     [parentView addSubview:content.view];      NSDictionary *views = @{                             @"childView" : content.view,                              };     NSArray *arr;      [content.view setTranslatesAutoresizingMaskIntoConstraints:NO];      arr = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[childView]-0-|" options:0 metrics:nil views:views];     [parentView addConstraints:arr];      arr = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[childView]-0-|" options:0 metrics:nil views:views];     [parentView addConstraints:arr];         [content didMoveToParentViewController:self]; }  

Answers 4

If it is just a view and your simply pushing view controllers on a navigation stack, then add your view to your navigation view controllers view.

[self.navigationController.view addSubview:view]; 
Read More