123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- //
- // KNSemiModalViewController.m
- // KNSemiModalViewController
- //
- // Created by Kent Nguyen on 2/5/12.
- // Copyright (c) 2012 Kent Nguyen. All rights reserved.
- //
- #import "UIViewController+KNSemiModal.h"
- #import <QuartzCore/QuartzCore.h>
- #import <objc/runtime.h>
- const struct KNSemiModalOption KNSemiModalOptionKeys = {
- .traverseParentHierarchy = @"KNSemiModalOptionTraverseParentHierarchy",
- .pushParentBack = @"KNSemiModalOptionPushParentBack",
- .animationDuration = @"KNSemiModalOptionAnimationDuration",
- .parentAlpha = @"KNSemiModalOptionParentAlpha",
- .parentScale = @"KNSemiModalOptionParentScale",
- .shadowOpacity = @"KNSemiModalOptionShadowOpacity",
- .transitionStyle = @"KNSemiModalTransitionStyle",
- .disableCancel = @"KNSemiModalOptionDisableCancel",
- .backgroundView = @"KNSemiModelOptionBackgroundView",
- .disableRotation = @"KNSemiModalOptionDisableRotation",
- };
- #define kSemiModalViewController @"PaPQC93kjgzUanz"
- #define kSemiModalDismissBlock @"l27h7RU2dzVfPoQ"
- #define kSemiModalPresentingViewController @"QKWuTQjUkWaO1Xr"
- #define kSemiModalOverlayTag 10001
- #define kSemiModalScreenshotTag 10002
- #define kSemiModalModalViewTag 10003
- #define kSemiModalDismissButtonTag 10004
- @interface UIViewController (KNSemiModalInternal)
- -(UIView*)parentTarget;
- -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward;
- @end
- @implementation UIViewController (KNSemiModalInternal)
- -(UIViewController*)kn_parentTargetViewController {
- UIViewController * target = self;
- // cover UINav & UITabbar as well
- while (target.parentViewController != nil) {
- target = target.parentViewController;
- }
-
- if ([[target ym_optionOrDefaultForKey:KNSemiModalOptionKeys.traverseParentHierarchy] boolValue]) {
- return target;
- }
- return self;
- }
- -(UIView*)parentTarget {
- return [self kn_parentTargetViewController].view;
- }
- - (UIViewController*)kn_targetToStoreValues {
- UIViewController * target = self;
- // cover UINav & UITabbar as well
- while (target.parentViewController != nil) {
- target = target.parentViewController;
- }
- return target;
- }
- #pragma mark Options and defaults
- -(void)kn_registerDefaultsAndOptions:(NSDictionary*)options {
- [[self kn_targetToStoreValues] ym_registerOptions:options defaults:@{
- KNSemiModalOptionKeys.traverseParentHierarchy : @(YES),
- KNSemiModalOptionKeys.pushParentBack : @(YES),
- KNSemiModalOptionKeys.animationDuration : @(0.5),
- KNSemiModalOptionKeys.parentAlpha : @(0.5),
- KNSemiModalOptionKeys.parentScale : @(0.8),
- KNSemiModalOptionKeys.shadowOpacity : @(0.8),
- KNSemiModalOptionKeys.transitionStyle : @(KNSemiModalTransitionStyleSlideUp),
- KNSemiModalOptionKeys.disableCancel : @(NO),
- KNSemiModalOptionKeys.disableRotation : @(NO),
- }];
- }
- #pragma mark Push-back animation group
- -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward {
- // Create animation keys, forwards and backwards
- CATransform3D t1 = CATransform3DIdentity;
- t1.m34 = 1.0/-900;
- t1 = CATransform3DScale(t1, 0.95, 0.95, 1);
- if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
- // The rotation angle is minor as the view is nearer
- t1 = CATransform3DRotate(t1, 7.5f*M_PI/180.0f, 1, 0, 0);
- } else {
- t1 = CATransform3DRotate(t1, 15.0f*M_PI/180.0f, 1, 0, 0);
- }
-
- CATransform3D t2 = CATransform3DIdentity;
- t2.m34 = t1.m34;
- double scale = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.parentScale] doubleValue];
- if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
- // Minor shift to mantai perspective
- t2 = CATransform3DTranslate(t2, 0, [self parentTarget].frame.size.height*-0.04, 0);
- t2 = CATransform3DScale(t2, scale, scale, 1);
- } else {
- t2 = CATransform3DTranslate(t2, 0, [self parentTarget].frame.size.height*-0.08, 0);
- t2 = CATransform3DScale(t2, scale, scale, 1);
- }
-
- CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
- animation.toValue = [NSValue valueWithCATransform3D:t1];
- CFTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
- animation.duration = duration/2;
- animation.fillMode = kCAFillModeForwards;
- animation.removedOnCompletion = NO;
- [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
-
- CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform"];
- animation2.toValue = [NSValue valueWithCATransform3D:(_forward?t2:CATransform3DIdentity)];
- animation2.beginTime = animation.duration;
- animation2.duration = animation.duration;
- animation2.fillMode = kCAFillModeForwards;
- animation2.removedOnCompletion = NO;
-
- CAAnimationGroup *group = [CAAnimationGroup animation];
- group.fillMode = kCAFillModeForwards;
- group.removedOnCompletion = NO;
- [group setDuration:animation.duration*2];
- [group setAnimations:[NSArray arrayWithObjects:animation,animation2, nil]];
- return group;
- }
- -(void)kn_interfaceOrientationDidChange:(NSNotification*)notification {
- if(![[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.disableRotation] boolValue]) {
- UIView *overlay = [[self parentTarget] viewWithTag:kSemiModalOverlayTag];
- [self kn_addOrUpdateParentScreenshotInView:overlay];
- }
- }
- -(UIImageView*)kn_addOrUpdateParentScreenshotInView:(UIView*)screenshotContainer {
- UIView *target = [self parentTarget];
- UIView *semiView = [target viewWithTag:kSemiModalModalViewTag];
-
- screenshotContainer.hidden = YES; // screenshot without the overlay!
- semiView.hidden = YES;
- UIGraphicsBeginImageContextWithOptions(target.bounds.size, YES, [[UIScreen mainScreen] scale]);
- if ([target respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
- [target drawViewHierarchyInRect:target.bounds afterScreenUpdates:YES];
- } else {
- [target.layer renderInContext:UIGraphicsGetCurrentContext()];
- }
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- screenshotContainer.hidden = NO;
- semiView.hidden = NO;
-
- UIImageView* screenshot = (id) [screenshotContainer viewWithTag:kSemiModalScreenshotTag];
- if (screenshot) {
- screenshot.image = image;
- }
- else {
- screenshot = [[UIImageView alloc] initWithImage:image];
- screenshot.tag = kSemiModalScreenshotTag;
- screenshot.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- [screenshotContainer addSubview:screenshot];
- }
- return screenshot;
- }
- @end
- @implementation UIViewController (KNSemiModal)
- -(void)presentSemiViewController:(UIViewController*)vc {
- [self presentSemiViewController:vc withOptions:nil completion:nil dismissBlock:nil];
- }
- -(void)presentSemiViewController:(UIViewController*)vc
- withOptions:(NSDictionary*)options {
- [self presentSemiViewController:vc withOptions:options completion:nil dismissBlock:nil];
- }
- -(void)presentSemiViewController:(UIViewController*)vc
- withOptions:(NSDictionary*)options
- completion:(KNTransitionCompletionBlock)completion
- dismissBlock:(KNTransitionCompletionBlock)dismissBlock {
- [self kn_registerDefaultsAndOptions:options]; // re-registering is OK
- UIViewController *targetParentVC = [self kn_parentTargetViewController];
- // implement view controller containment for the semi-modal view controller
- [targetParentVC addChildViewController:vc];
- if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) {
- [vc beginAppearanceTransition:YES animated:YES]; // iOS 6
- }
- objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalViewController, vc, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalDismissBlock, dismissBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
- [self presentSemiView:vc.view withOptions:options completion:^{
- [vc didMoveToParentViewController:targetParentVC];
- if ([vc respondsToSelector:@selector(endAppearanceTransition)]) {
- [vc endAppearanceTransition]; // iOS 6
- }
- if (completion) {
- completion();
- }
- }];
- }
- -(void)presentSemiView:(UIView*)view {
- [self presentSemiView:view withOptions:nil completion:nil];
- }
- -(void)presentSemiView:(UIView*)view withOptions:(NSDictionary*)options {
- [self presentSemiView:view withOptions:options completion:nil];
- }
- -(void)presentSemiView:(UIView*)view
- withOptions:(NSDictionary*)options
- completion:(KNTransitionCompletionBlock)completion {
- [self kn_registerDefaultsAndOptions:options]; // re-registering is OK
- UIView * target = [self parentTarget];
-
- if (![target.subviews containsObject:view]) {
- // Set associative object
- objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalPresentingViewController, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- // Register for orientation changes, so we can update the presenting controller screenshot
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(kn_interfaceOrientationDidChange:)
- name:UIDeviceOrientationDidChangeNotification
- object:nil];
- // Get transition style
- NSUInteger transitionStyle = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue];
-
- // Calulate all frames
- CGFloat semiViewHeight = view.frame.size.height;
- CGRect vf = target.bounds;
- CGRect semiViewFrame;
- if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
- // We center the view and mantain aspect ration
- semiViewFrame = CGRectMake((vf.size.width - view.frame.size.width) / 2.0, vf.size.height-semiViewHeight, view.frame.size.width, semiViewHeight);
- } else {
- semiViewFrame = CGRectMake(0, vf.size.height-semiViewHeight, vf.size.width, semiViewHeight);
- }
-
- CGRect overlayFrame = CGRectMake(0, 0, vf.size.width, vf.size.height-semiViewHeight);
-
- // Add semi overlay
- UIView *overlay;
- UIView *backgroundView = [[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.backgroundView];
- if (backgroundView) {
- overlay = backgroundView;
- } else {
- overlay = [[UIView alloc] init];
- }
-
- overlay.frame = target.bounds;
- overlay.backgroundColor = [UIColor blackColor];
- overlay.userInteractionEnabled = YES;
- overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- overlay.tag = kSemiModalOverlayTag;
-
- // Take screenshot and scale
- UIImageView *ss = [self kn_addOrUpdateParentScreenshotInView:overlay];
- [target addSubview:overlay];
-
- // Dismiss button (if allow)
- if(![[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.disableCancel] boolValue]) {
- // Don't use UITapGestureRecognizer to avoid complex handling
- UIButton * dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
- [dismissButton addTarget:self action:@selector(dismissSemiModalView) forControlEvents:UIControlEventTouchUpInside];
- dismissButton.backgroundColor = [UIColor clearColor];
- dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- dismissButton.frame = overlayFrame;
- dismissButton.tag = kSemiModalDismissButtonTag;
- [overlay addSubview:dismissButton];
- }
-
- // Begin overlay animation
- if ([[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) {
- [ss.layer addAnimation:[self animationGroupForward:YES] forKey:@"pushedBackAnimation"];
- }
- NSTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
- [UIView animateWithDuration:duration animations:^{
- ss.alpha = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.parentAlpha] floatValue];
- }];
-
- // Present view animated
- view.frame = (transitionStyle == KNSemiModalTransitionStyleSlideUp
- ? CGRectOffset(semiViewFrame, 0, +semiViewHeight)
- : semiViewFrame);
- if (transitionStyle == KNSemiModalTransitionStyleFadeIn || transitionStyle == KNSemiModalTransitionStyleFadeInOut) {
- view.alpha = 0.0;
- }
-
- if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
- // Don't resize the view width on rotating
- view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
- } else {
- view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
- }
-
- view.tag = kSemiModalModalViewTag;
- [target addSubview:view];
- view.layer.shadowColor = [[UIColor blackColor] CGColor];
- view.layer.shadowOffset = CGSizeMake(0, -2);
- view.layer.shadowRadius = 5.0;
- view.layer.shadowOpacity = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.shadowOpacity] floatValue];
- view.layer.shouldRasterize = YES;
- view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
-
- [UIView animateWithDuration:duration animations:^{
- if (transitionStyle == KNSemiModalTransitionStyleSlideUp) {
- view.frame = semiViewFrame;
- } else if (transitionStyle == KNSemiModalTransitionStyleFadeIn || transitionStyle == KNSemiModalTransitionStyleFadeInOut) {
- view.alpha = 1.0;
- }
- } completion:^(BOOL finished) {
- if (!finished) return;
- [[NSNotificationCenter defaultCenter] postNotificationName:kSemiModalDidShowNotification
- object:self];
- if (completion) {
- completion();
- }
- }];
- }
- }
- -(void)updateBackground{
- UIView * target = [self parentTarget];
- UIView * overlay = [target viewWithTag:kSemiModalOverlayTag];
- [self kn_addOrUpdateParentScreenshotInView:overlay];
- }
- -(void)dismissSemiModalView {
- [self dismissSemiModalViewWithCompletion:nil];
- }
- -(void)dismissSemiModalViewWithCompletion:(void (^)(void))completion {
- // Look for presenting controller if available
- UIViewController * presentingController = objc_getAssociatedObject([self kn_targetToStoreValues], kSemiModalPresentingViewController);
- if (presentingController) {
- objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalPresentingViewController, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- [presentingController dismissSemiModalViewWithCompletion:completion];
- return;
- }
- // Correct target for dismissal
- UIView * target = [self parentTarget];
- UIView * modal = [target viewWithTag:kSemiModalModalViewTag];
- UIView * overlay = [target viewWithTag:kSemiModalOverlayTag];
- NSUInteger transitionStyle = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue];
- NSTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
- UIViewController *vc = objc_getAssociatedObject([self kn_targetToStoreValues], kSemiModalViewController);
- KNTransitionCompletionBlock dismissBlock = objc_getAssociatedObject([self kn_targetToStoreValues], kSemiModalDismissBlock);
-
- // Child controller containment
- [vc willMoveToParentViewController:nil];
- if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) {
- [vc beginAppearanceTransition:NO animated:YES]; // iOS 6
- }
-
- [UIView animateWithDuration:duration animations:^{
- if (transitionStyle == KNSemiModalTransitionStyleSlideUp) {
- if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
- // As the view is centered, we perform a vertical translation
- modal.frame = CGRectMake((target.bounds.size.width - modal.frame.size.width) / 2.0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
- } else {
- modal.frame = CGRectMake(0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
- }
- } else if (transitionStyle == KNSemiModalTransitionStyleFadeOut || transitionStyle == KNSemiModalTransitionStyleFadeInOut) {
- modal.alpha = 0.0;
- }
- } completion:^(BOOL finished) {
- [overlay removeFromSuperview];
- [modal removeFromSuperview];
-
- // Child controller containment
- [vc removeFromParentViewController];
- if ([vc respondsToSelector:@selector(endAppearanceTransition)]) {
- [vc endAppearanceTransition];
- }
-
- if (dismissBlock) {
- dismissBlock();
- }
-
- objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalDismissBlock, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
- objc_setAssociatedObject([self kn_targetToStoreValues], kSemiModalViewController, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
- }];
-
- // Begin overlay animation
- UIImageView * ss = (UIImageView*)[overlay.subviews objectAtIndex:0];
- if ([[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) {
- [ss.layer addAnimation:[self animationGroupForward:NO] forKey:@"bringForwardAnimation"];
- }
- [UIView animateWithDuration:duration animations:^{
- ss.alpha = 1;
- } completion:^(BOOL finished) {
- if(finished){
- [[NSNotificationCenter defaultCenter] postNotificationName:kSemiModalDidHideNotification
- object:self];
- if (completion) {
- completion();
- }
- }
- }];
- }
- - (void)resizeSemiView:(CGSize)newSize {
- UIView * target = [self parentTarget];
- UIView * modal = [target viewWithTag:kSemiModalModalViewTag];
- CGRect mf = modal.frame;
- mf.size.width = newSize.width;
- mf.size.height = newSize.height;
- mf.origin.y = target.frame.size.height - mf.size.height;
- UIView * overlay = [target viewWithTag:kSemiModalOverlayTag];
- UIButton * button = (UIButton*)[overlay viewWithTag:kSemiModalDismissButtonTag];
- CGRect bf = button.frame;
- bf.size.height = overlay.frame.size.height - newSize.height;
- NSTimeInterval duration = [[[self kn_targetToStoreValues] ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue];
- [UIView animateWithDuration:duration animations:^{
- modal.frame = mf;
- button.frame = bf;
- } completion:^(BOOL finished) {
- if(finished){
- [[NSNotificationCenter defaultCenter] postNotificationName:kSemiModalWasResizedNotification
- object:self];
- }
- }];
- }
- @end
- #pragma mark - NSObject (YMOptionsAndDefaults)
- // NSObject+YMOptionsAndDefaults
- // Created by YangMeyer on 08.10.12.
- // Copyright (c) 2012 Yang Meyer. All rights reserved.
- #import <objc/runtime.h>
- @implementation NSObject (YMOptionsAndDefaults)
- static char const * const kYMStandardOptionsTableName = "YMStandardOptionsTableName";
- static char const * const kYMStandardDefaultsTableName = "YMStandardDefaultsTableName";
- - (void)ym_registerOptions:(NSDictionary *)options
- defaults:(NSDictionary *)defaults
- {
- objc_setAssociatedObject(self, kYMStandardOptionsTableName, options, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- objc_setAssociatedObject(self, kYMStandardDefaultsTableName, defaults, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (id)ym_optionOrDefaultForKey:(NSString*)optionKey
- {
- NSDictionary *options = objc_getAssociatedObject(self, kYMStandardOptionsTableName);
- NSDictionary *defaults = objc_getAssociatedObject(self, kYMStandardDefaultsTableName);
- NSAssert(defaults, @"Defaults must have been set when accessing options.");
- return options[optionKey] ?: defaults[optionKey];
- }
- @end
- #pragma mark - UIView (FindUIViewController)
- // Convenient category method to find actual ViewController that contains a view
- // Adapted from: http://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview-on-iphone
- @implementation UIView (FindUIViewController)
- - (UIViewController *) containingViewController {
- UIView * target = self.superview ? self.superview : self;
- return (UIViewController *)[target traverseResponderChainForUIViewController];
- }
- - (id) traverseResponderChainForUIViewController {
- id nextResponder = [self nextResponder];
- BOOL isViewController = [nextResponder isKindOfClass:[UIViewController class]];
- BOOL isTabBarController = [nextResponder isKindOfClass:[UITabBarController class]];
- if (isViewController && !isTabBarController) {
- return nextResponder;
- } else if(isTabBarController){
- UITabBarController *tabBarController = nextResponder;
- return [tabBarController selectedViewController];
- } else if ([nextResponder isKindOfClass:[UIView class]]) {
- return [nextResponder traverseResponderChainForUIViewController];
- } else {
- return nil;
- }
- }
- @end
|