App development process-right sliding return gesture function, app gesture
After iOS7, the navigation Controller provides the gesture function to slide from the left edge of the screen to the right.
However, if you customize the return button in the navigation bar, this function becomes invalid and needs to be implemented by yourself. If you need to modify the trigger range of a gesture, you still need to implement it on your own.
A widely used implementation scheme is to use private variables and APIs to complete gesture interaction and return functions, customize gesture trigger conditions and additional functions.
Another implementation scheme is to use the proxy method of UINavigationController for interaction and Animation:
-(Nullable id <UIViewControllerInteractiveTransitioning>) navigationController :( UINavigationController *) navigationController
InteractionControllerForAnimationController :( id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS (7_0 );
-(Nullable id <UIViewControllerAnimatedTransitioning>) navigationController :( UINavigationController *) navigationController
AnimationControllerForOperation :( UINavigationControllerOperation) operation
FromViewController :( UIViewController *) fromVC
ToViewController :( UIViewController *) toVC NS_AVAILABLE_IOS (7_0 );
The former features convenience, but can only use the interaction and animation defined by the system. The latter features high customization, but requires additional interaction protocols and animation protocols.
Uses private variables and APIs to implement the right-sliding return gesture function.
First look at the core logic:
-(Void) base_pushViewController :( UIViewController *) viewController animated :( BOOL) animated {if (! [Self. interactivepopgsturerecognizer. view. gestureRecognizers containsObject: self. base_panGestureRecognizer]) {[self. interactivepopgsturerecognizer. view addGestureRecognizer: self. base_panGestureRecognizer]; // use KVC to obtain private variables and APIs to implement native pop gesture effect NSArray * internalTargets = [self. interactivepopgsturerecognizer valueForKey: @ "targets"]; id internalTarget = [internalTargets. firstObject valueForKey: @ "target "]; SEL internalAction = NSSelectorFromString (@" handleNavigationTransition: "); self. base_panGestureRecognizer.delegate = [self base_panGestureRecognizerDelegateObject]; [self. base_panGestureRecognizer addTarget: internalTarget action: internalAction]; self. interactivepopgsturerecognizer. enabled = NO;} [self base_setupViewControllerBasedNavigationBarAppearanceIfNeeded: viewController]; if (! [Self. viewControllers containsObject: viewController]) {[self base_pushViewController: viewController animated: animated];}
The interactivepopgsturerecognizer attribute of UINavigationController is a gesture recognition object used by the system to pop up the navigation stack of viewController. There is a private variable named "targets", similar to NSArray. The first element object of this array, there is a private variable named "target", which is the object that achieves the expected interaction. This object has a private method named "handleNavigationTransition:", that is, the target method.
Add a custom UIPanGestureRecognizer on the UIView (self. interactivepopgsturerecognizer. view) of the returned gesture interaction, and use the proxy method gestureRecognizerShouldBegin implemented by the delegate object to control the gesture effective. Finally, if you disable the system's returned Gesture Recognition object, you can use the customized pan gesture to call the system's pop interaction and animation.
The code used to determine whether the pan gesture can trigger the return operation is as follows:
-(BOOL) gestureRecognizerShouldBegin :( UIGestureRecognizer *) gestureRecognizer {// transfer in if ([self. navigationController valueForKey: @ "_ isTransitioning"] boolValue]) {return NO;} // in the navigation controller's root Controller Interface if (self. navigationController. viewControllers. count <= 1) {return NO;} UIViewController * popedController = [self. navigationController. viewControllers lastObject]; if (popedController. base_popgsturedisabled) {return NO;} // valid gesture range: CGPoint beginningLocation = [gestureRecognizer locationInView: gestureRecognizer. view]; CGFloat popgsture1_tivedistancefromleftedge = popedController. base_popgsture1_tivedistancefromleftedge; if (popgsture1_tivedistancefromleftedge> 0 & beginningLocation. x> identifier) {return NO;} // The right slide gesture UIPanGestureRecognizer * panGesture = (identifier *) gestureRecognizer; CGPoint transition = [panGesture translationInView: panGesture. view]; if (transition. x <= 0) {return NO;} return YES ;}
NavigationController also uses the private variable "_ isTransitioning" to determine whether the interaction is in progress.
To enable interactive animation in the navigation bar, you must call the display or hidden animation method in the viewWillAppear method of UIViewController by using the swizzle method, therefore, you need to add a code block for delayed execution:
-(Void) base_setupViewControllerBasedNavigationBarAppearanceIfNeeded :( UIViewController *) appearingViewController {// if navigationController does not display the navigation bar, return directly if (self. navigationBarHidden) {return;} _ weak typeof (self) weakSelf = self; ViewControllerViewWillAppearDelayBlock block = ^ (UIViewController * viewController, BOOL animated) {_ strong typeof (weakSelf) strongSelf = weakSelf; if (strongSelf ){[ StrongSelf setNavigationBarHidden: viewController. incluanimated: animated] ;}}; appearingViewController. Metadata = block; UIViewController * disappearingViewController = self. viewControllers. lastObject; if (disappearingViewController &&! DisappearingViewController. viewWillAppearDelayBlock) {disappearingViewController. viewWillAppearDelayBlock = block ;}}
Some runtime types and methods are used in the complete classification process.
1. reference the header file # import <objc/runtime. h>
2. Add attributes to a category. The following methods are involved:
Void objc_setAssociatedObject (id object, const void * key, id value, objc_AssociationPolicy policy)
Id objc_getAssociatedObject (id object, const void * key)
For example, the get/set Method of the base_currentNavigationBarHidden attribute added in UIViewController (popgsture:
- (BOOL)base_currentNavigationBarHidden{ NSNumber *number = objc_getAssociatedObject(self, _cmd); if (number) { return number.boolValue; } self.base_currentNavigationBarHidden = NO; return NO;}- (void)setBase_currentNavigationBarHidden:(BOOL)hidden{ self.canUseViewWillAppearDelayBlock = YES; objc_setAssociatedObject(self, @selector(base_currentNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
The first parameter is generally self.
The second parameter const void * key requires an address.
You can declare a static char * key; or static NSString * key;. It doesn't matter whether the value is assigned, because only the address is needed and the parameter is & key.
The above Code uses _ cmd and @ selector for the same purpose. _ Cmd returns the SEL of the current method and @ selector returns the SEL of the target method, that is, the function address.
The third parameter is the associated value.
The fourth Parameter policy, which is of the enumeration type, basically corresponds to the keyword related to attribute reference:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */};
3. Understand Other runtime types or methods
Typedef struct objc_method * Method; // An opaque type that represents a method in a class definition.
Method class_getInstanceMethod (Class cls, SEL name) // return the instance Method
Method class_getClassMethod (Class cls, SEL name) // return Class Method
IMP method_getImplementation (Method m) // Returns the implementation of a method.
Const char * method_getTypeEncoding (Method m) // Returns a string describing a method's parameter and return types.
BOOL class_addMethod (Class cls, SEL name, IMP imp, const char * types) // Adds a new method to a class with a given name and implementation.
IMP class_replaceMethod (Class cls, SEL name, IMP imp, const char * types) // Replaces the implementation of a method for a given class.
Void method_exchangeImplementations (Method m1, Method m2) // Exchanges the implementations of two methods.
The above method can achieve the goal of the swizzle method. It can replace the function address of the newly added method in the category with the existing method (because at runtime, the name of the method to be executed is still viewWillAppear:, But it points to the new method address. You can also call the current method name in the new method code (after switching, the current method name points to the old method address ). For example, the following code in UIViewController:
+ (Void) load {_ weak typeof (self) weakSelf = self; static dispatch_once_t once; dispatch_once (& once, ^ {[weakSelf attributes: @ selector (viewWillAppear :) withNewSelector: @ selector (base_viewWillAppear :)]; [weakSelf swizzleOriginalSelector: @ selector (viewDidDisappear :) withNewSelector: @ selector (timer :)] ;}+ (void) Then :( SEL) originalSelector withNewSelector :( SEL) newSelector {Class selfClass = [self class]; Method originalMethod = compile (selfClass, originalSelector); Method newMethod = class_getInstanceMethod (selfClass, newSelector ); IMP originalIMP = originalMethod); IMP newIMP = method_getImplementation (newMethod); // Add the new IMP to the original SEL with BOOL addSuccess = class_addMethod (selfClass, originalSelector, newIMP, method_getTypeEncoding (newMethod); if (addSuccess) {values (selfClass, newSelector, originalIMP, method_getTypeEncoding (originalMethod);} else {values (originalMethod, newMethod );}} -(void) base_viewWillAppear :( BOOL) animated {[self base_viewWillAppear: animated]; if (self. canUseViewWillAppearDelayBlock & self. viewWillAppearDelayBlock) {self. viewWillAppearDelayBlock (self, animated);} if (self. transitionCoordinator) {[self. transitionCoordinator policywheninteractionendsusingblock: ^ (id <UIViewControllerTransitionCoordinatorContext> _ Nonnull context) {if ([context isCancelled]) {self. base_isBeingPoped = NO ;}}] ;}}
Note:
A. + the load static method will be called when this classification is added to the runtime (Invoked whenever a class or category is added to the Objective-C runtime), and the execution sequence is after the class's own + load method.
B. If base_currentNavigationBarHidden is not explicitly set in use, canUseViewWillAppearDelayBlock is NO. Because the parent class I encapsulate provides similar functions, you do not need to enable the same functions in the category. This function is intended to be provided to friends who directly integrate categories.
C. The transitionCoordinator attribute of UIViewController. This attribute has a value when there is a field interaction on the current interface. At the end of interaction, you can call back a block to inform the interaction status and related attributes. The base_isBeingPoped attribute is declared here to mark whether the current view controller is being pop out of the navigation stack. If the interaction is canceled, set it to NO. In the end, you can go to viewDidDisappear: method.
Use the proxy method of UINavigationController to implement a highly customized solution. The record will be updated next time.
Base project Updated: git@github.com: ALongWay/base. git