Showing posts with label uiview. Show all posts
Showing posts with label uiview. Show all posts

Wednesday, October 25, 2017

How do I transition/animate color of UINavigationBar?

Leave a Comment

I have been searching for how to transition/animate the barTintColor of a UINavigationBar for a while now, and I only see different answers. Some use UIView.animateWithDuration, some use CATransition, but the most interesting ones, like this one use animate(alongsideTransition animation.., which I like the sound of, but I can't get it working properly. Am I doing something wrong?

Many specify that I can simply use the transitionCoordinator in viewWillAppear:. I have set up a fresh super tiny project like this:

class RootViewController:UIViewController{ //Only subclassed     override func viewWillAppear(_ animated: Bool) {         super.viewWillAppear(animated)         transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in             self?.setNavigationColors()             }, completion: nil)     }     func setNavigationColors(){         //Override in subclasses     } }  class FirstViewController: RootViewController {     override func viewDidLoad() {         super.viewDidLoad()         self.title = "First"     }     override func setNavigationColors(){         navigationController?.navigationBar.barTintColor = UIColor.white         navigationController?.navigationBar.tintColor = UIColor.black         navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.black]         navigationController?.navigationBar.barStyle = UIBarStyle.default     } } class SecondViewController: RootViewController {     override func viewDidLoad() {         super.viewDidLoad()         self.title = "Second"     }     override func setNavigationColors(){         navigationController?.navigationBar.barTintColor = UIColor.black         navigationController?.navigationBar.tintColor = UIColor.white         navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]         navigationController?.navigationBar.barStyle = UIBarStyle.black     } } 

With this code, this happens: First

  • The push-transition from First to Second looks perfect. All elements transition perfectly, maybe except the StatusBar, which instantly changes to white. I'd rather know how to transition it, but I'll accept it for now.
  • The pop-transition from Second to First is completely wrong. It keeps the colors from Second until the transition is completely done.
  • The drag-transition from Second to First looks alright, when dragging all the way over. Again, the StatusBar instantly becomes black as soon as I start dragging, but I don't know if that's possible to fix.
  • The drag-transition from Second to First but cancelled mid-drag and returning to Second is completely screwed up. It looks fine until Second is completely back in control, and then it suddenly changes itself to First-colors. This should not happen.

I made a few changes to my RootViewController to make it a little better. I removed viewWillAppear: completely, and changed it with this:

class RootViewController:UIViewController{      override func willMove(toParentViewController parent: UIViewController?) {         if let last = self.navigationController?.viewControllers.last as? RootViewController{             if last == self && self.navigationController!.viewControllers.count > 1{                 if let parent = self.navigationController!.viewControllers[self.navigationController!.viewControllers.count - 2] as? RootViewController{                     parent.setNavigationColors()                 }             }         }     }     override func viewWillDisappear(_ animated: Bool) {         if let parent = navigationController?.viewControllers.last as? RootViewController{             parent.animateNavigationColors()         }     }     override func viewDidAppear(_ animated: Bool) {         self.setNavigationColors()     }      func animateNavigationColors(){         transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in             self?.setNavigationColors()             }, completion: nil)     }     func setNavigationColors(){         //Override in subclasses     } } 

With this updated code, I get this: Second

A few observations:

  • The transition from First to Second is the same
  • The pop-transition from Second to First is now animating correctly, except from the back-arrow, the back-text (and the statusBar, but yeah..). These are instantly changed to black. In the first gif, you could see that the back-arrow and the back-text also transitioned.
  • The drag-transition from Second to First also has this problem, the back-arrow and back-text are suddenly instantly black when starting. The barTint is fixed so that it doesn't get the wrong color when cancelling the drag.

What am I doing wrong? How am I supposed to do this?

What I want is to transition all elements smoothly. The tint of the back-button, the back-text, the title, the barTint, and the statusBar. Is this not possible?

3 Answers

Answers 1

You can overwrite the push and pop methods of UINavigationController to set the bar color. I've stored the bar color corresponding to a view controller in its navigation item with a custom subclass of UINavigationItem. The following code works for me in iOS 11 for full and for interactive transitions as well:

import UIKit  class NavigationItem: UINavigationItem {     @IBInspectable public var barTintColor: UIColor? }  class NavigationController: UINavigationController, UIGestureRecognizerDelegate {     func applyTint(_ navigationItem: UINavigationItem?) {         if let item = navigationItem as? NavigationItem {             self.navigationBar.barTintColor = item.barTintColor         }     }      override func viewWillAppear(_ animated: Bool) {         super.viewWillAppear(animated)         applyTint(self.topViewController?.navigationItem)         self.interactivePopGestureRecognizer?.delegate = self     }     override func pushViewController(_ viewController: UIViewController, animated: Bool) {         applyTint(viewController.navigationItem)         super.pushViewController(viewController, animated: animated)     }      override func popViewController(animated: Bool) -> UIViewController? {         let viewController = super.popViewController(animated: animated)          applyTint(self.topViewController?.navigationItem)         return viewController     }      override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {         let result = super.popToViewController(viewController, animated: animated)          applyTint(viewController.navigationItem)         return result     }      override func popToRootViewController(animated: Bool) -> [UIViewController]? {         let result = super.popToRootViewController(animated: animated)          applyTint(self.topViewController?.navigationItem)         return result     }      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {         return true     }      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {         return (otherGestureRecognizer is UIScreenEdgePanGestureRecognizer)     } } 

Note: The coordination of the color animation is done by the navigation controller

Answers 2

I updated my previous answer. I did animation effect without using transition coordinator and they are smooth in every case like push/pop/swipe-back.

https://stackoverflow.com/a/40272975/5433235

Also, you can check it on my github project

Hope it helps you :)

Answers 3

See my answer on the same question: How to set navigation bar to transparent in iOS 11

You can set navbar to transparent and animate view below it.

Read More

Wednesday, September 13, 2017

How to rotate a UIView around it's proper center without distortion / skew

Leave a Comment

I've built my own animation engine, and I want to set the rotation of a UI view on each frame that I render (60fps)

I've made a video now showing what the problem is currently.

It's fairly close, but it's still rotating in a strange way:

https://www.youtube.com/watch?v=1ZKK4r0-6i4

I've implemented a custom CustomUIView class, that inherits from UI view. This has translation, scaling and rotation propertys, so when I transform the matrix, all 3 happen in the same action.

var t = CGAffineTransform( translationX: self.translation.x, y:  self.translation.y ); // Apply scaling t = t.scaledBy( x: self.scaling.x, y: self.scaling.y ); // Apply rotation t = t.rotated(by:self.rotation) self.transform = t 

and the size and the width are set like this:

view.frame.size.width = parseSize(value, axis: "width") view.layer.bounds.size.width = parseSize(value, axis: "width")     

I'm trying to set both of these properties, but I'm not sure if this is correct.

I've set the anchor point a lot, and also tried the overall center point of the view as well.

3 Answers

Answers 1

The problem was that I was setting the transform more than once per animation frame. This was causing a compound effect with different values. You need to organise your variables so that you only set the transform once during the property modifications.

Answers 2

Instead of using

var t = CGAffineTransform( translationX: self.translation.x, y:  self.translation.y ); 

use

var t = CGAffineTransform(rotationAngle: CGFloat(Double.pi*self.rotation/180.0)) 

this will rotate your view from a center without changing their position.

Answers 3

This should help you to make it work according to your requirement:

@IBOutlet var scaleRotateImage: UIImageView! func scaleNTransform() -> Void {      scaleRotateImage.layer.cornerRadius = scaleRotateImage.frame.size.height/2.0     scaleRotateImage.clipsToBounds = true      let scaleTransform = CGAffineTransform(scaleX: 3.0, y: 3.0)  // Scale     let rotateTransform = CGAffineTransform(rotationAngle: CGFloat.pi) // Rotation     let hybridTransform = scaleTransform.concatenating(rotateTransform) // Both Scale + Rotation      // Outer block of animation     UIView.animate(withDuration: 5.0, animations: {         self.scaleRotateImage.transform = scaleTransform         self.view.layoutIfNeeded()     }) { (isCompleted) in          // Nested block of animation         UIView.animate(withDuration: 5.0, animations: {             self.scaleRotateImage.transform = hybridTransform             self.view.layoutIfNeeded()         })      }   } 


Result

enter image description here

Read More

Thursday, March 30, 2017

Break a loop inside a animation block

Leave a Comment

I'm trying to break a for-loop after a completed UIView animation. Here is the following snippet:

public func greedyColoring() {     let colors = [UIColor.blue, UIColor.green, UIColor.yellow, UIColor.red, UIColor.cyan, UIColor.orange, UIColor.magenta, UIColor.purple]      for vertexIndex in 0 ..< self.graph.vertexCount {         let neighbours = self.graph.neighborsForIndex(vertexIndex)         let originVertex = vertices[vertexIndex]          print("Checking now Following neighbours for vertex \(vertexIndex): \(neighbours)")          var doesNotMatch = false          while doesNotMatch == false {             inner: for color in colors{                 UIView.animate(withDuration: 1, delay: 2, options: .curveEaseIn, animations: {                     originVertex.layer.backgroundColor = color.cgColor                 }, completion: { (complet) in                     if complet {                         let matches = neighbours.filter {                             let vertIdx = Int($0)!                              print("Neighbour to check: \(vertIdx)")                              let vertex = self.vertices[vertIdx-1]                              if vertex.backgroundColor == color{                                 return true                             }else{                                 return false                             }                         }                          //print("there were \(matches.count) matches")                          if matches.count == 0 {                             // do some things                             originVertex.backgroundColor = color                             doesNotMatch = true                             break inner                         } else {                             doesNotMatch = false                         }                     }                 })             }         }     } } 

Basically this method iterate over a Graph and checks every vertex and its neighbours and give the vertex a color that none of its neighbours has. Thats why it has to break the iteration at the first color that hasn't been used. I tried to use Unlabeled loops but it still doesn't compile (break is only allowed inside a loop). The problem is that I would like to visualise which colors have been tested. Thats why I'm using the UIView.animate()

Is there anyway to solve my problem?

4 Answers

Answers 1

You need to understand, that the completion block that you pass to the animate function is called after the animation has finished, which is a long time (in computer time) after your for loop has iterated through the colors array. You set the duration to be 1 second, which means that the completion is called 1 second later. Since the for loop isn't waiting for your animation to finish, they will all start animating at the same time (off by some milliseconds perhaps). The for loop has completed way before the animation has completed, which is why it doesn't make sense to break the for loop, since it is no longer running!

If you want to see this add a print("Fire") call right before the UIView.animate function is called and print("Finished") in the completion block. In the console you should see all the fire before all the finished.

You should instead queue the animations, so that they start and finish one after the other.

Answers 2

As Frederik mentioned, the animations the order in execution of the next in your for loop is not synchronous with the order of your completions. This means that the for loop will keep cycling no matter of your blocks implementations. An easy fix would be to create the animations by using CABasicAnimation and CAAnimationGroup. So that the product of your for loop would be a chain of animations stacked in an animation group.

This tutorial will give you an idea on how to use CABasicAnimation and CAAnimationGroup: https://www.raywenderlich.com/102590/how-to-create-a-complex-loading-animation-in-swift

You can use this approach, because the condition to break your for loop is given by parameters that are not dependent by the animation itself. The animations will be executed anyway once they will be attached to the view you are trying to animate.

Hope this helps.

Answers 3

Just modifying your code a little bit. Its a old school recursion but should work. Assuming all the instance variables are available here is a new version.      public func greedyColoring(vertex:Int,colorIndex:Int){        if  vertexIndex > self.graph.vertexCount { return }         if colorIndex > color.count {return}         let neighbours = self.graph.neighborsForIndex(vertexIndex)         let originVertex = vertices[vertexIndex]         let color = self.colors[colorIndex]       UIView.animate(withDuration: 1, delay: 2, options: .curveEaseIn,   animations: {             originVertex.layer.backgroundColor = color.cgColor         }, completion: { (complet) in             if complet {                 let matches = neighbours.filter {                     let vertIdx = Int($0)!                      print("Neighbour to check: \(vertIdx)")                      let vertex = self.vertices[vertIdx-1]                      //No idea what you are trying to do here. So leaving as it is.                     if vertex.backgroundColor == color{                         return true                     }else{                         return false                     }                 }                  //print("there were \(matches.count) matches")                  if matches.count == 0 {                     // do some things                     originVertex.backgroundColor = color                     greedyColoring(vertex: vertex++,colorIndex:0)                 } else {                     greedyColoring(vertex: vertex,colorIndex: colorIndex++)                 }             }         })     }   Now we can call this function simply      greedyColor(vertex:0,colorIndex:0) 

Answers 4

As mentioned before, the completion blocks are all called asynchronously, and thus do not exist within the for loop. Your best bet is to call a common method from within the completion blocks which will ignore everything after the first call.

var firstMatch: UIColor?  func foundMatch (_ colour: UIColor) {     if firstMatch == nil     {         firstMatch = colour         print (firstMatch?.description ?? "Something went Wrong")     } }   let colours: [UIColor] = [.red, .green, .blue]  for colour in colours {     UIView.animate(withDuration: 1.0, animations: {}, completion:         { (success) in             if success { foundMatch (colour) }         }) } 
Read More

Tuesday, January 3, 2017

Programmatic auto-layout, constraints in SWTableViewCell

Leave a Comment

I'm trying to add a detail view to SWTableViewCell.

The constraints are tied to all elements of the cell, and I'm trying to get a detailed view to animate from the table view cell.

Photo of what is now versus what is desired:

constraints change size of the cell, when it's desired to have a subview only be resized

I've been spending a lot of time experimenting blindly, and trying to do a deep dive in manual auto-layout. I haven't been able to identify how to get the desired result. I attempted to transfer all the contents to another subview, however, removing elements from self did not solve this.

Code with the constraints:

- (void)initializer { layoutUpdating = NO; // Set up scroll view that will host our cell content self.cellScrollView = [[SWCellScrollView alloc] init]; self.cellScrollView.translatesAutoresizingMaskIntoConstraints = NO; self.cellScrollView.delegate = self; self.cellScrollView.showsHorizontalScrollIndicator = NO; self.cellScrollView.scrollsToTop = NO; self.cellScrollView.scrollEnabled = YES;  _contentCellView = [[UIView alloc] init]; [self.cellScrollView addSubview:_contentCellView];  // Add the cell scroll view to the cell UIView *contentViewParent = self; UIView *clipViewParent = self.cellScrollView; if (![NSStringFromClass([[self.subviews objectAtIndex:0] class]) isEqualToString:kTableViewCellContentView]) {     // iOS 7     contentViewParent = [self.subviews objectAtIndex:0];     clipViewParent = self; } NSArray *cellSubviews = [contentViewParent subviews]; [self insertSubview:self.cellScrollView atIndex:0]; for (UIView *subview in cellSubviews) {     [_contentCellView addSubview:subview]; }  // Set scroll view to perpetually have same frame as self. Specifying relative to superview doesn't work, since the latter UITableViewCellScrollView has different behaviour. [self addConstraints:@[                        [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0],                        [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0],                        [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0],                        [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0],                        ]];  self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTapped:)]; self.tapGestureRecognizer.cancelsTouchesInView = NO; self.tapGestureRecognizer.delegate             = self; [self.cellScrollView addGestureRecognizer:self.tapGestureRecognizer];  self.longPressGestureRecognizer = [[SWLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewPressed:)]; self.longPressGestureRecognizer.cancelsTouchesInView = NO; self.longPressGestureRecognizer.minimumPressDuration = kLongPressMinimumDuration; self.longPressGestureRecognizer.delegate = self; [self.cellScrollView addGestureRecognizer:self.longPressGestureRecognizer];  // Create the left and right utility button views, as well as vanilla UIViews in which to embed them.  We can manipulate the latter in order to effect clipping according to scroll position. // Such an approach is necessary in order for the utility views to sit on top to get taps, as well as allow the backgroundColor (and private UITableViewCellBackgroundView) to work properly.  self.leftUtilityClipView = [[UIView alloc] init]; self.leftUtilityClipConstraint = [NSLayoutConstraint constraintWithItem:self.leftUtilityClipView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]; self.leftUtilityButtonsView = [[SWUtilityButtonView alloc] initWithUtilityButtons:nil                                                                        parentCell:self                                                             utilityButtonSelector:@selector(leftUtilityButtonHandler:)];  self.rightUtilityClipView = [[UIView alloc] initWithFrame:self.bounds]; self.rightUtilityClipConstraint = [NSLayoutConstraint constraintWithItem:self.rightUtilityClipView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]; self.rightUtilityButtonsView = [[SWUtilityButtonView alloc] initWithUtilityButtons:nil                                                                         parentCell:self                                                              utilityButtonSelector:@selector(rightUtilityButtonHandler:)];   UIView *clipViews[] = { self.rightUtilityClipView, self.leftUtilityClipView }; NSLayoutConstraint *clipConstraints[] = { self.rightUtilityClipConstraint, self.leftUtilityClipConstraint }; UIView *buttonViews[] = { self.rightUtilityButtonsView, self.leftUtilityButtonsView }; NSLayoutAttribute alignmentAttributes[] = { NSLayoutAttributeRight, NSLayoutAttributeLeft };  for (NSUInteger i = 0; i < 2; ++i) {     UIView *clipView = clipViews[i];     NSLayoutConstraint *clipConstraint = clipConstraints[i];     UIView *buttonView = buttonViews[i];     NSLayoutAttribute alignmentAttribute = alignmentAttributes[i];      clipConstraint.priority = UILayoutPriorityDefaultHigh;      clipView.translatesAutoresizingMaskIntoConstraints = NO;     clipView.clipsToBounds = YES;      [clipViewParent addSubview:clipView];     [self addConstraints:@[                            // Pin the clipping view to the appropriate outer edges of the cell.                            [NSLayoutConstraint constraintWithItem:clipView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0],                            [NSLayoutConstraint constraintWithItem:clipView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0],                            [NSLayoutConstraint constraintWithItem:clipView attribute:alignmentAttribute relatedBy:NSLayoutRelationEqual toItem:self attribute:alignmentAttribute multiplier:1.0 constant:0.0],                            clipConstraint,                            ]];      [clipView addSubview:buttonView];     [self addConstraints:@[                            // Pin the button view to the appropriate outer edges of its clipping view.                            [NSLayoutConstraint constraintWithItem:buttonView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:clipView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0],                            [NSLayoutConstraint constraintWithItem:buttonView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:clipView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0],                            [NSLayoutConstraint constraintWithItem:buttonView attribute:alignmentAttribute relatedBy:NSLayoutRelationEqual toItem:clipView attribute:alignmentAttribute multiplier:1.0 constant:0.0],                             // Constrain the maximum button width so that at least a button's worth of contentView is left visible. (The button view will shrink accordingly.)                            [NSLayoutConstraint constraintWithItem:buttonView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.contentView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:-kUtilityButtonWidthDefault],                            ]]; }  self.utilityView = [[UIView alloc] initWithFrame: CGRectMake(0, self.bounds.size.height, self.bounds.size.width, 0)]; [self.utilityView setBackgroundColor: [UIColor darkGrayColor]]; [self addSubview: self.utilityView]; [self sendSubviewToBack: self.utilityView]; } 

The table view calls a public method within beginUpdates and endUpdates, and the animation is UIView animateWithDuration:.

The view hierarchy:

   <UIWindow: 0x7f85cfe11840; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x608000242d60>; layer = <UIWindowLayer: 0x60800002c300>>   | <UIView: 0x7f85cff08bc0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x618000027040>>   |    | <UIView: 0x7f85cfc03ce0; frame = (0 0; 414 66); layer = <CALayer: 0x60000002e4c0>>   |    |    | <LBHamburgerButton: 0x7f85cfc0cd20; baseClass = UIButton; frame = (314 10; 50 50); opaque = NO; layer = <CALayer: 0x60000002e620>>   |    |    |    | <CAShapeLayer: 0x60800022cce0> (layer)   |    |    |    | <CAShapeLayer: 0x60800022d840> (layer)   |    |    |    | <CAShapeLayer: 0x60800022d920> (layer)   |    | <UIView: 0x7f85cfc08e70; frame = (0 66; 414 670); layer = <CALayer: 0x60000002e5a0>>   |    |    | <UIView: 0x7f85cff16790; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x61800002c240>>   |    |    |    | <iCarousel: 0x7f85cff16930; frame = (0 10; 414 80); autoresize = W+H; layer = <CALayer: 0x61800002c260>>   |    |    |    |    | <UIView: 0x7f85cff16f10; frame = (0 0; 414 80); autoresize = W+H; gestureRecognizers = <NSArray: 0x618000241f80>; layer = <CALayer: 0x61800002c280>>   |    |    |    |    |    | <UIView: 0x7f85cfc13a70; frame = (108.998 -0.000800016; 196.004 80.0016); layer = <CALayer: 0x60000002fea0>>   |    |    |    |    |    |    | <UILabel: 0x7f85cff1ebb0; frame = (0 0; 196 80); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6180002819a0>>   |    |    |    |    |    | <UIView: 0x7f85cff24150; frame = (-125.656 -18.322; 225.889 116.644); userInteractionEnabled = NO; layer = <CALayer: 0x6180000279a0>>   |    |    |    |    |    |    | <UILabel: 0x7f85cfd1af40; frame = (0 0; 196 80); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x610000488660>>   |    |    |    |    |    | <UIView: 0x7f85cfc1c280; frame = (313.767 -18.322; 225.889 116.644); userInteractionEnabled = NO; layer = <CALayer: 0x6000000309e0>>   |    |    |    |    |    |    | <UILabel: 0x7f85cfd1b1c0; frame = (0 0; 196 80); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x610000489380>>   |    |    |    |    |    | <UIView: 0x7f85cfc19630; frame = (-444.013 -99.6275; 306.865 279.255); hidden = YES; userInteractionEnabled = NO; layer = <CALayer: 0x600000030540>>   |    |    |    |    |    |    | <UILabel: 0x7f85cfe29790; frame = (0 0; 196 80); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009aae0>>   |    |    |    | <UIView: 0x7f85cff17590; frame = (0 95; 414 60); layer = <CALayer: 0x61800002c3e0>>   |    |    |    |    | <UIButton: 0x7f85cff17730; frame = (0 0; 60 60); opaque = NO; layer = <CALayer: 0x61800002c440>>   |    |    |    |    | <UIButton: 0x7f85cff179f0; frame = (354 0; 60 60); opaque = NO; tag = 1; layer = <CALayer: 0x61800002c460>>   |    |    |    |    | <UILabel: 0x7f85cff17cb0; frame = (107 0; 200 60); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6180002873f0>>   |    |    |    | <UIView: 0x7f85cff189d0; frame = (0 158; 414 512); layer = <CALayer: 0x61800002c620>>   |    |    |    |    | <UITableView: 0x7f85d1842000; frame = (0 0; 414 512); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x618000242c70>; layer = <CALayer: 0x61800002cba0>; contentOffset: {0, 0}; contentSize: {414, 8160.0099999997765}>   |    |    |    |    |    | <UITableViewWrapperView: 0x7f85d1845a00; frame = (0 0; 414 512); gestureRecognizers = <NSArray: 0x618000242fa0>; layer = <CALayer: 0x61800002cca0>; contentOffset: {0, 0}; contentSize: {414, 512}>   |    |    |    |    |    |    | <CurrentOrdersTableViewCell: 0x7f85d1821800; baseClass = UITableViewCell; frame = (0 640; 414 80); hidden = YES; autoresize = W; layer = <CALayer: 0x618000031400>>   |    |    |    |    |    |    |    | <UIView: 0x7f85cff23fb0; frame = (0 44; 320 0); layer = <CALayer: 0x618000032540>>   |    |    |    |    |    |    |    | <SWCellScrollView: 0x7f85d1876000; baseClass = UIScrollView; frame = (0 0; 414 80); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x618000249450>; layer = <CALayer: 0x6180000314c0>; contentOffset: {90, 0}; contentSize: {594, 80}>   |    |    |    |    |    |    |    |    | <UIView: 0x7f85cff22ac0; frame = (90 0; 414 79.6667); layer = <CALayer: 0x6180000315c0>>   |    |    |    |    |    |    |    |    |    | <UITableViewCellContentView: 0x7f85cff224a0; frame = (0 0; 414 79.6667); gestureRecognizers = <NSArray: 0x618000249300>; layer = <CALayer: 0x618000031440>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cff25470; frame = (20 0; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800028aaf0>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cff256f0; frame = (96.8 0; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800028ab90>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cff25970; frame = (173.6 0; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800028ac30>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cff25bf0; frame = (250.4 0; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800028acd0>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cff25e70; frame = (327.2 0; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800028ad70>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cff260f0; frame = (20 15; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800028ae10>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cfe264a0; frame = (96.8 15; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009ba30>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cfe26720; frame = (173.6 15; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009bad0>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cfe269a0; frame = (250.4 15; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009bb70>>   |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x7f85cfe270e0; frame = (327.2 15; 66.8 12); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009bc10>>   |    |    |    |    |    |    |    |    | <UIView: 0x7f85cff23a10; frame = (504 0; 0 80); clipsToBounds = YES; hidden = YES; layer = <CALayer: 0x618000031c00>>   |    |    |    |    |    |    |    |    |    | <SWUtilityButtonView: 0x7f85cff23bb0; frame = (-90 0; 90 80); layer = <CALayer: 0x618000031c60>>   |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x7f85cfe28a20; frame = (0 0; 90 80); opaque = NO; gestureRecognizers = <NSArray: 0x60800024c2d0>; layer = <CALayer: 0x608000234900>>   |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x7f85cfe28ce0; frame = (11 29.3333; 68.3333 21.6667); opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009c570>>   |    |    |    |    |    |    |    |    | <UIView: 0x7f85cff236b0; frame = (90 0; 0 80); clipsToBounds = YES; hidden = YES; layer = <CALayer: 0x618000031b40>>   |    |    |    |    |    |    |    |    |    | <SWUtilityButtonView: 0x7f85cff23850; frame = (0 0; 90 80); layer = <CALayer: 0x618000031ba0>>   |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x7f85cfe27360; frame = (0 0; 90 80); opaque = NO; gestureRecognizers = <NSArray: 0x60800024aa70>; layer = <CALayer: 0x6080002334e0>>   |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x7f85cfe27620; frame = (8.66667 29.3333; 73 21.6667); opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60800009bd00>>   |    |    |    |    |    |    |    |    | <UIImageView: 0x7f85cfe290c0; frame = (498.667 3; 2.33333 74); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x608000234f20>>   |    |    |    |    |    |    |    | <_UITableViewCellSeparatorView: 0x7f85cfe28680; frame = (0 79.6667; 414 0.333333); layer = <CALayer: 0x6080002347a0>>   |    |    |    |    |    |    |  

0 Answers

Read More

Thursday, April 21, 2016

Apply CAShapeLayer to main UIView except UIButton

Leave a Comment

Here is a code which applying mask to the whole UIView:

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.view.bounds]; [maskPath appendPath:[UIBezierPath bezierPathWithArcCenter:self.mapCardsButton.center                                                     radius:self.mapCardsButton.frame.size.height/2.0f                                                 startAngle:0.0f                                                   endAngle:2.0f*M_PI                                                  clockwise:NO]]; CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.fillRule  = kCAFillRuleEvenOdd; maskLayer.fillColor = [UIColor blackColor].CGColor; maskLayer.path = maskPath.CGPath; self.view.layer.mask = maskLayer; 

The problem is that I want to apply the mask above to the whole UIView except one specific UIButton(mapCardsButton) which is also on the same UIView. Is it possible to do?

UPD: I tried

[self.view.layer insertSublayer:maskLayer atIndex:0]; 

instead of

self.view.layer.mask = maskLayer; 

but my self.view lost alpha channel and animation of maskLayer doesn't work anymore

Here is a project with code: https://www.dropbox.com/s/b94qcwxzoi23kwk/test_04092016.zip?dl=0

2 Answers

Answers 1

The simplest way would be for the UIButton to be a sibling view of the masked view, rather than a subview. I realise that it might make logical sense for the button to be a subview (especially since it looks like your code is probably from a UIViewController subclass), so I would create a container UIView that holds all the other subviews apart from mapCardsButton and apply the mask to this new view.

So say you called the new view maskedContainerView, then:

  • self.view has two subviews: maskedContainerView and mapCardsButton
  • maskedContainerView holds all the subviews that self.view used to, except mapCardsButton
  • maskedContainerView.layer.mask = maskLayer.

Answers 2

Why don't you try the following? instead of the following stack:

• mask(view) -> button

make the view background color clear, at an additional subview and do the following:

• view -> mask(subView), button

This way you will have the same visual effect, but use the bottom view as a container. In code:

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.view.bounds]; [maskPath appendPath:[UIBezierPath bezierPathWithArcCenter:self.mapCardsButton.center                                                     radius:self.mapCardsButton.frame.size.height/2.0f                                                 startAngle:0.0f                                                   endAngle:2.0f*M_PI                                                  clockwise:NO]]; CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.fillRule  = kCAFillRuleEvenOdd; maskLayer.fillColor = [UIColor blackColor].CGColor; maskLayer.path = maskPath.CGPath;  UIView *maskedView = [[UIView alloc] initWithFrame:self.view.bounds];     maskedView.mask = maskLayer;  self.view.backgroundColor = [UIColor clearColor]; [self.view addSubView:maskedView]; [self.view addSubView:self.mapCardsButton]; 
Read More

Thursday, April 14, 2016

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