版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.07.02 |
前言
在app中,我們經常需要點擊別人分享發布的圖片或者頭像,然后放大縮小等,還可以保存到本地相冊等。感興趣的可以看看我寫的其他小技巧。
1. 實用小技巧(一):UIScrollView中上下左右滾動方向的判斷
2. 實用小技巧(二):屏幕橫豎屏的判斷和相關邏輯
3.實用小技巧(三):點擊手勢屏蔽子視圖的響應
4.實用小技巧(四):動態的增刪標簽視圖
5.實用小技巧(五):通過相冊或者相機更改圖標
6.實用小技巧(六):打印ios里所有字體
7. 實用小技巧(七):UITableViewCell自適應行高的計算
8. 實用小技巧(八):數字余額顯示的分隔
9.實用小技巧(九):類頭條模糊背景的實現
10.實用小技巧(十):晃動手機換后臺服務器網絡
11.實用小技巧(十一):scrollView及其子類顯示的一些異常處理
12.實用小技巧(十二):頭像圖片縮放以及保存到相冊簡單功能的實現
功能需求
??酷我音樂盒有的頁面可以左右用手拖動,當大于一定的角度就可以dismiss或者其他操作,類似的在阿里巴巴的釘釘中也有應用,其實簡單來說就是一個pan手勢以及錨點和position的應用,下面我們就簡單的實現個效果。
功能實現
下面我們就直接看代碼吧。
先看一下代碼結構。
下面我們就看一下詳細代碼。
1. AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
JJMusicVC *musicVC = [[JJMusicVC alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:musicVC];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
2. JJMusicVC.h
#import <UIKit/UIKit.h>
@interface JJMusicVC : UIViewController
@end
3.JJMusicVC.m
#import "JJMusicVC.h"
#import "JJMusciPlayVC.h"
@interface JJMusicVC ()
@property (nonatomic, strong) UIButton *playButton;
@end
@implementation JJMusicVC
#pragma mark - Object Private Function
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
}
#pragma mark - Object Private Function
- (void)setupUI
{
self.view.backgroundColor = [UIColor lightGrayColor];
UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom];
[playButton setTitle:@"播放音樂" forState:UIControlStateNormal];
[playButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
playButton.titleLabel.font = [UIFont boldSystemFontOfSize:20.0];
playButton.frame = CGRectMake((self.view.bounds.size.width - CGRectGetWidth(playButton.frame)) * 0.5, (self.view.bounds.size.height - CGRectGetHeight(playButton.frame)) * 0.5, 100.0, 30.0);
[playButton addTarget:self action:@selector(playButtonDidClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:playButton];
self.playButton = playButton;
}
#pragma mark - Action && Notification
- (void)playButtonDidClick
{
JJMusciPlayVC *playVC = [[JJMusciPlayVC alloc] init];
[self presentViewController:playVC animated:YES completion:nil];
}
@end
4. JJMusciPlayVC.h
#import <UIKit/UIKit.h>
@interface JJMusciPlayVC : UIViewController
@end
5. JJMusciPlayVC.m
#import "JJMusciPlayVC.h"
#import "JJMusicAnimation.h"
@interface JJMusciPlayVC ()
@property (nonatomic, strong) JJMusicAnimation *animation;
@property (nonatomic, strong) UIImageView *playImageView;
@end
@implementation JJMusciPlayVC
- (instancetype)init
{
self = [super init];
if (self) {
self.modalPresentationStyle = UIModalPresentationCustom;
//這幾行不能放在viewDidLoad里面,否則后面就是黑屏的
JJMusicAnimation *animation = [[JJMusicAnimation alloc] init];
self.transitioningDelegate = animation;
self.animation = animation;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
}
#pragma mark - Object Private Function
- (void)setupUI
{
self.view.backgroundColor = [UIColor lightGrayColor];
UIImageView *playImageView = [[UIImageView alloc] initWithFrame:self.view.frame];
playImageView.userInteractionEnabled = YES;
playImageView.image = [UIImage imageNamed:@"music"];
[self.view addSubview:playImageView];
self.playImageView = playImageView;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
[self.view addGestureRecognizer:pan];
}
#pragma mark - Action && Notification
- (void)panAction:(UIPanGestureRecognizer *)sender
{
// 1.獲取用戶的手指拖拽的偏移量
CGFloat offsetX = [sender translationInView:self.view].x;
// 2.將獲取的偏移量,轉為角度
CGFloat angle = offsetX / self.view.bounds.size.width * M_PI_4;
switch (sender.state)
{
case UIGestureRecognizerStateBegan:
// 修改錨點,進行軸的偏移;
self.view.layer.anchorPoint = CGPointMake(0.5, 1.5);
// 修改position,實現正常顯示!
self.view.layer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 1.5);
case UIGestureRecognizerStateChanged:
self.view.transform = CGAffineTransformMakeRotation(angle);
break;
case UIGestureRecognizerStateEnded:
{
// 需要判斷,旋轉的角度 如果大于了某一值 dimiss 否則,歸位!
if (ABS(angle) > 0.33) {
[self dismissViewControllerAnimated:YES completion:nil];
break;
}
}
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
{
// 歸位!
[UIView animateWithDuration:.3 animations:^{
self.view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
// 恢復錨點
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
//不能使用self.view.center->它的值目前還是之前修改過的position的值!太大!
self.view.layer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
}];
}
break;
default:
break;
}
}
@end
6. JJMusicAnimation.h
#import <UIKit/UIKit.h>
@interface JJMusicAnimation : NSObject <UIViewControllerTransitioningDelegate>
@end
7. JJMusicAnimation.m
#import "JJMusicAnimation.h"
@interface JJMusicAnimation () <UIViewControllerAnimatedTransitioning>
@end
@implementation JJMusicAnimation
#pragma mark - UIViewControllerTransitioningDelegate
/*
參數1 presented 被展示出來的控制器對象! twoController
參數2 presenting 正在顯示別的控制器的那個對象! viewController
參數3 source 源控制器,一般跟參數2是同一個對象! viewController
*/
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
NSLog(@"參數1%@ 參數2%@ 參數3%@", presented, presenting, source);
return self;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
// 轉場動畫的效果!
// transitionContext 轉場的上下文
// 相當于是轉場動畫的舞臺! 里面提供了我們做動畫需要的所有信息!
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
// 1.獲取容器視圖
UIView *containerV = [transitionContext containerView];
// 2.獲取要顯示的控制器
UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
toVc.view.frame = containerV.bounds;
// 3.展示控制器的視圖到界面!
[containerV addSubview:toVc.view];
// 4.通過動畫進行實現旋轉的方式顯示視圖!
// 1.修改旋轉的軸!
toVc.view.layer.anchorPoint = CGPointMake(0.5, 1.5);
toVc.view.layer.position = CGPointMake(toVc.view.bounds.size.width * 0.5, toVc.view.bounds.size.height * 1.5);
// 2.以旋轉的方式展示
// - 轉動 -90°
toVc.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
// - 動畫顯示
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toVc.view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
toVc.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
toVc.view.layer.position = CGPointMake(toVc.view.bounds.size.width * 0.5, toVc.view.bounds.size.height * 0.5);
//告訴系統動畫執行完了!要不然界面不能點!
[transitionContext completeTransition:YES];
}];
}
@end
至此,所有代碼就結束了。
功能效果
下面我們就看一下功能效果。
知識點補充
我們這里用到了兩個重要的概念就是anchor point 和 position。
- anchorPoint: 每一個UIView都默認關聯著一個CALayer, UIView有frame、bounds和center三個屬性,CALayer也有類似的屬性,分別為frame、bounds、position、anchorPoint,而anchorPoint會移動layer的位置。下面我們就舉例說一下anchorPoint的使用。
??從一個例子開始入手吧,想象一下,把一張A4白紙用圖釘訂在書桌上,如果訂得不是很緊的話,白紙就可以沿順時針或逆時針方向圍繞圖釘旋轉,這時候圖釘就起著支點的作用。我們要解釋的anchorPoint就相當于白紙上的圖釘,它主要的作用就是用來作為變換的支點,旋轉就是一種變換,類似的還有平移、縮放。繼續擴展,很明顯,白紙的旋轉形態隨圖釘的位置不同而不同,圖釘訂在白紙的正中間與左上角時分別造就了兩種旋轉形態,這是由圖釘(anchorPoint)的位置決定的。如何衡量圖釘(anchorPoint)在白紙中的位置呢?在iOS中,anchorPoint點的值是用一種相對bounds的比例值來確定的,在白紙的左上角、右下角,anchorPoint分為為(0,0), (1, 1),也就是說anchorPoint是在單元坐標空間(同時也是左手坐標系)中定義的。類似地,可以得出在白紙的中心點、左下角和右上角的anchorPoint為(0.5,0.5), (0,1), (1,0)。如下圖所示。
position: 確切地說,position是layer中的anchorPoint點在superLayer中的位置坐標。因此可以說, position點是相對superLayer的,anchorPoint點是相對layer的,兩者是相對不同的坐標空間的一個重合點。 再來看看position的原始定義: The layer’s position in its superlayer’s coordinate space。 可以理解成position是layer相對superLayer坐標空間的位置,很顯然,這里的位置是根據anchorPoint來確定的.
anchorPoint、position、frame:anchorPoint的默認值為(0.5,0.5),也就是anchorPoint默認在layer的中心點。默認情況下,使用addSublayer函數添加layer時,如果已知layer的frame值,根據上面的結論,那么position的值便可以用下面的公式計算:
position.x = frame.origin.x + 0.5 * bounds.size.width;
position.y = frame.origin.y + 0.5 * bounds.size.height;
里面的0.5是因為anchorPoint取默認值,更通用的公式應該是下面的:
position.x = frame.origin.x + anchorPoint.x *
bounds.size.width;
position.y = frame.origin.y + anchorPoint.y *
bounds.size.height;
- 特別注意:下面再來看另外兩個問題,如果單方面修改layer的position位置,會對anchorPoint有什么影響呢?修改anchorPoint又如何影響position呢?
根據代碼測試,兩者互不影響,受影響的只會是frame.origin,也就是layer坐標原點相對superLayer會有所改變。換句話說,frame.origin由position和anchorPoint共同決定,上面的公式可以變換成下面這樣的:
frame.origin.x = position.x - anchorPoint.x *
bounds.size.width;
frame.origin.y = position.y - anchorPoint.y *
bounds.size.height;
這就解釋了為什么修改anchorPoint會移動layer,因為position不受影響,只能是frame.origin做相應的改變,因而會移動layer。
- 總結
- position是layer中的anchorPoint在superLayer中的位置坐標.
- 互不影響原則:單獨修改position與anchorPoint中任何一個屬性都不影響另一個屬性。
- frame、position與anchorPoint有以下關系:
frame.origin.x = position.x - anchorPoint.x *
bounds.size.width;
frame.origin.y = position.y - anchorPoint.y *
bounds.size.height;
后記
未完,待續~~~