Fork me on GitHub

iOS 系统控件的一些用法

UIView

transform 属性

CGAffineTransformMake 变换原理

1
@property(nonatomic) CGAffineTransform transform;   // default is CGAffineTransformIdentity. animatable
  • 当我们使用 CGAffineTransformMake 方法时实际是创建一个
    CGAffineTransform的结构体,此结构体定义如下:
1
2
3
4
5
6
7
8
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};

CG_EXTERN CGAffineTransform CGAffineTransformMake(CGFloat a, CGFloat b,
CGFloat c, CGFloat d, CGFloat tx, CGFloat ty)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
  • 其中a` b cd txty可以组成如下3X3` 的矩阵,如下:
1
2
3
4
5
6
B = 
[ ]
a b 0
c d 0
tx ty 1
[ ]
  • 当使用 Transform 变换时实际是把view上所有的点通过 矩阵B计算得新的点。假设 view上一点
1
A = [x y 1]
  • A * B 可以得到变换后的点
1
A` = [(a*x+c*y+tx) (b*x+d*y+ty) (1) ]

其中的 矩阵 * 矩阵 的计算可参考大学 线性代数
使用给不同的字值自然可以达到不同效果

  • 其中Apple封装了一些常用的方法,包括:
    平移(Translation)
    缩放(Scale)
    旋转(Rotation)
    剪切(Shear)
    翻转(Flip)

  • 平移(Translation)

    由上面的计算可以得到 当

1
2
3
4
a = 1
c = 0
b = 0
d = 1

1
A` = [(x+tx) (y+ty) (1) ]

使用
CGAffineTransformMake(1, 0, 0, 1, tx, ty);即可。
系统提供了类似的方法

1
2
3
4
5
/* Return a transform which translates by `(tx, ty)':
t' = [ 1 0 0 1 tx ty ] */

CG_EXTERN CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx,
CGFloat ty) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

Apple注释可以发现前面的分析正确,那么其他的种类已基本一个思路。

参考

此属性主要是用来操作UIView的平移,旋转,伸缩。

  • CGAffineTransformIdentity (默认。当我们想恢复原始状态时可以使用此方法)

  • CGAffineTransformMakeRotation 旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[UIView animateWithDuration:0.3 animations:^{
// 0 < x <= M_PI 顺时针旋转
// -M_PI <= x < 0 逆时针旋转
// 总结:总是以最近,尽量顺时针,最多旋转180°的方式到达目的地
/*
self.myView.transform = CGAffineTransformMakeRotation(M_PI); // 顺时针旋转 180°
self.myView.transform = CGAffineTransformMakeRotation(M_PI_2); // 顺时针旋转 90°
self.myView.transform = CGAffineTransformMakeRotation(M_PI_4); // 顺时针旋转 45°

self.myView.transform = CGAffineTransformMakeRotation(-M_PI); // 逆时针旋转 180°
self.myView.transform = CGAffineTransformMakeRotation(-M_PI_2); // 逆时针旋转 90°
self.myView.transform = CGAffineTransformMakeRotation(-M_PI_4); // 逆时针旋转 45°
*/
self.myView.transform = CGAffineTransformMakeRotation(M_PI * 1.5); // 逆时针旋转 90°
}];
  • 演示如下:

  • CGAffineTransformMakeScale 伸缩
1
2
3
4
5
6
[UIView animateWithDuration:0.3 animations:^{
// 宽高伸缩比例
// 宽变为 sx * width
// 高变为 sy * height
self.myView.transform = CGAffineTransformMakeScale(0.6, 0.5); // 宽变为 0.6 * height 高变为 0.5 * height
}];
  • CGAffineTransformMakeTranslation 平移操作
1
2
3
4
5
6
7
[UIView animateWithDuration:0.3 animations:^{
// xy移动距离
// x变为 x + tx
// y变为 y + ty
self.myView.transform = CGAffineTransformMakeTranslation(10, 10);
}];
}];

系统API说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
结构体
B = [ ]
a b 0
c d 0
tx ty 1
[ ]

view 上所有点经过 变换为 A`,变换为 * B
A(X,Y,1) * B = A`([(a*x+c*y+tx),(b*x+d*y+ty),(1))
*/
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};

/* The identity transform: [ 1 0 0 1 0 0 ]. */
// [(a*x+c*y+tx) (b*x+d*y+ty) (1) ] --> [(x) (y) (1) ] 还原初始位置
// 还原初始状态
CG_EXTERN const CGAffineTransform CGAffineTransformIdentity
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Return the transform [ a b c d tx ty ]. */
// [(a*x+c*y+tx) (b*x+d*y+ty) (1) ] --> [(a*x+c*y+tx) (b*x+d*y+ty) (1) ]
// 自定义形变
CG_EXTERN CGAffineTransform CGAffineTransformMake(CGFloat a, CGFloat b,
CGFloat c, CGFloat d, CGFloat tx, CGFloat ty)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Return a transform which translates by `(tx, ty)':
t' = [ 1 0 0 1 tx ty ] */
// [(a*x+c*y+tx) (b*x+d*y+ty) (1) ] --> [(x+tx) (y+ty) (1) ] 平移
// 平移
CG_EXTERN CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx,
CGFloat ty) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Return a transform which scales by `(sx, sy)':
t' = [ sx 0 0 sy 0 0 ] */
// [(a*x+c*y+tx) (b*x+d*y+ty) (1) ] --> [(sx*x) (sy*y) (1) ]
// 缩放
CG_EXTERN CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Return a transform which rotates by `angle' radians:
t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] */
// 旋转
CG_EXTERN CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Return true if `t' is the identity transform, false otherwise. */
// 是否为初始值
CG_EXTERN bool CGAffineTransformIsIdentity(CGAffineTransform t)
CG_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);

/* Translate `t' by `(tx, ty)' and return the result:
t' = [ 1 0 0 1 tx ty ] * t */
// 在 t' 的状态下发生下一次变换平移
CG_EXTERN CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t,
CGFloat tx, CGFloat ty) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Scale `t' by `(sx, sy)' and return the result:
t' = [ sx 0 0 sy 0 0 ] * t */
// 在 t' 的状态下发生下一次变换 缩放
CG_EXTERN CGAffineTransform CGAffineTransformScale(CGAffineTransform t,
CGFloat sx, CGFloat sy) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Rotate `t' by `angle' radians and return the result:
t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t */
// 在 t' 的状态下发生下一次变换 旋转
CG_EXTERN CGAffineTransform CGAffineTransformRotate(CGAffineTransform t,
CGFloat angle) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Invert `t' and return the result. If `t' has zero determinant, then `t'
is returned unchanged. */
// 对t 反向旋转
CG_EXTERN CGAffineTransform CGAffineTransformInvert(CGAffineTransform t)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Concatenate `t2' to `t1' and return the result:
t' = t1 * t2 */
// 组合 t1 t2
CG_EXTERN CGAffineTransform CGAffineTransformConcat(CGAffineTransform t1,
CGAffineTransform t2) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Return true if `t1' and `t2' are equal, false otherwise. */
// 判断是否相同
CG_EXTERN bool CGAffineTransformEqualToTransform(CGAffineTransform t1,
CGAffineTransform t2) CG_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);

/* Transform `point' by `t' and return the result:
p' = p * t
where p = [ x y 1 ]. */
// 得到新的中心CGPoint (没用过)
CG_EXTERN CGPoint CGPointApplyAffineTransform(CGPoint point,
CGAffineTransform t) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Transform `size' by `t' and return the result:
s' = s * t
where s = [ width height 0 ]. */
// 得到新的size CGSize (没用过)
CG_EXTERN CGSize CGSizeApplyAffineTransform(CGSize size, CGAffineTransform t)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

/* Transform `rect' by `t' and return the result. Since affine transforms do
not preserve rectangles in general, this function returns the smallest
rectangle which contains the transformed corner points of `rect'. If `t'
consists solely of scales, flips and translations, then the returned
rectangle coincides with the rectangle constructed from the four
transformed corners. */
// 得到新的rect CGRect (没用过)
CG_EXTERN CGRect CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t)
CG_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);

UIScrollView

UIScrollView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@property(nonatomic)         CGPoint                      contentOffset;                  // default CGPointZero 当前滚动到的位置
@property(nonatomic) CGSize contentSize; // default CGSizeZero 滚动范围
@property(nonatomic) UIEdgeInsets contentInset; // default UIEdgeInsetsZero. add additional scroll area around content 额外增加的滚动范围
@property(nullable,nonatomic,weak) id<UIScrollViewDelegate> delegate; // default nil. weak reference 代理

// default NO. if YES, try to lock vertical or horizontal scrolling while dragging
// 方向锁定(在一个方向滚动时 另一个方向不允许滚动)
@property(nonatomic,getter=isDirectionalLockEnabled) BOOL directionalLockEnabled;

// default YES. if YES, bounces past edge of content and back again 弹簧效果
@property(nonatomic) BOOL bounces;

// default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag vertically
// 垂直方向的弹簧效果
@property(nonatomic) BOOL alwaysBounceVertical;

// default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag horizontally
// 水平方向的弹簧效果
@property(nonatomic) BOOL alwaysBounceHorizontal;

// default NO. if YES, stop on multiples of view bounds
// 启动分页
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled __TVOS_PROHIBITED;

// default YES. turn off any dragging temporarily
// 是否可以拖动
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled;

// default YES. show indicator while we are tracking. fades out after tracking
// 是否显示水平滚动条
@property(nonatomic) BOOL showsHorizontalScrollIndicator;
// default YES. show indicator while we are tracking. fades out after tracking
// 是否显示垂直滚动条
@property(nonatomic) BOOL showsVerticalScrollIndicator;

// default is UIEdgeInsetsZero. adjust indicators inside of insets
// 调整滚动显示器的边距
@property(nonatomic) UIEdgeInsets scrollIndicatorInsets;

// default is UIScrollViewIndicatorStyleDefault
// scrollView 的样式
@property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle;

// UIKIT_EXTERN const CGFloat UIScrollViewDecelerationRateNormal NS_AVAILABLE_IOS(3_0);
// UIKIT_EXTERN const CGFloat UIScrollViewDecelerationRateFast NS_AVAILABLE_IOS(3_0);
// 滚动的速度
@property(nonatomic) CGFloat decelerationRate NS_AVAILABLE_IOS(3_0);

// tvos 上使用
@property(nonatomic) UIScrollViewIndexDisplayMode indexDisplayMode API_AVAILABLE(tvos(10.2));

// 设置滚动的位置 是否动画
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; // animate at constant velocity to new offset

// 滚动到具体位置
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated; // scroll so rect is just visible (nearest edges). nothing if rect completely visible

// 让滚动条显示(但只会显示一段时间会自动隐藏 可以使用分类重写imageView的 alpha 方法来控制,因为scroll的滚动条是 imageView)
- (void)flashScrollIndicators; // displays the scroll indicators for a short time. This should be done whenever you bring the scroll view to front.

/*
Scrolling with no scroll bars is a bit complex. on touch down, we don't know if the user will want to scroll or track a subview like a control.
on touch down, we start a timer and also look at any movement. if the time elapses without sufficient change in position, we start sending events to
the hit view in the content subview. if the user then drags far enough, we switch back to dragging and cancel any tracking in the subview.
the methods below are called by the scroll view and give subclasses override points to add in custom behaviour.
you can remove the delay in delivery of touchesBegan:withEvent: to subviews by setting delaysContentTouches to NO.
*/

// 是否正在跟踪
@property(nonatomic,readonly,getter=isTracking) BOOL tracking; // returns YES if user has touched. may not yet have started dragging

// 是否正在拖拽
@property(nonatomic,readonly,getter=isDragging) BOOL dragging; // returns YES if user has started scrolling. this may require some time and or distance to move to initiate dragging

// 是否正在减速
@property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating; // returns YES if user isn't dragging (touch up) but scroll view is still moving

@property(nonatomic) BOOL delaysContentTouches; // default is YES. if NO, we immediately call -touchesShouldBegin:withEvent:inContentView:. this has no effect on presses
@property(nonatomic) BOOL canCancelContentTouches; // default is YES. if NO, then once we start tracking, we don't try to drag if the touch moves. this has no effect on presses

// override points for subclasses to control delivery of touch events to subviews of the scroll view
// called before touches are delivered to a subview of the scroll view. if it returns NO the touches will not be delivered to the subview
// this has no effect on presses
// default returns YES
- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event inContentView:(UIView *)view;
// called before scrolling begins if touches have already been delivered to a subview of the scroll view. if it returns NO the touches will continue to be delivered to the subview and scrolling will not occur
// not called if canCancelContentTouches is NO. default returns YES if view isn't a UIControl
// this has no effect on presses
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;

/*
the following properties and methods are for zooming. as the user tracks with two fingers, we adjust the offset and the scale of the content. When the gesture ends, you should update the content
as necessary. Note that the gesture can end and a finger could still be down. While the gesture is in progress, we do not send any tracking calls to the subview.
the delegate must implement both viewForZoomingInScrollView: and scrollViewDidEndZooming:withView:atScale: in order for zooming to work and the max/min zoom scale must be different
note that we are not scaling the actual scroll view but the 'content view' returned by the delegate. the delegate must return a subview, not the scroll view itself, from viewForZoomingInScrollview:
*/

@property(nonatomic) CGFloat minimumZoomScale; // default is 1.0
@property(nonatomic) CGFloat maximumZoomScale; // default is 1.0. must be > minimum zoom scale to enable zooming

@property(nonatomic) CGFloat zoomScale NS_AVAILABLE_IOS(3_0); // default is 1.0
- (void)setZoomScale:(CGFloat)scale animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);

@property(nonatomic) BOOL bouncesZoom; // default is YES. if set, user can go past min/max zoom while gesturing and the zoom will animate to the min/max value at gesture end

@property(nonatomic,readonly,getter=isZooming) BOOL zooming; // returns YES if user in zoom gesture
@property(nonatomic,readonly,getter=isZoomBouncing) BOOL zoomBouncing; // returns YES if we are in the middle of zooming back to the min/max value

// When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top, but only if its `scrollsToTop` property is YES, its delegate does not return NO from `shouldScrollViewScrollToTop`, and it is not already at the top.
// On iPhone, we execute this gesture only if there's one on-screen scroll view with `scrollsToTop` == YES. If more than one is found, none will be scrolled.
@property(nonatomic) BOOL scrollsToTop __TVOS_PROHIBITED; // default is YES.

// Use these accessors to configure the scroll view's built-in gesture recognizers.
// Do not change the gestures' delegates or override the getters for these properties.

// Change `panGestureRecognizer.allowedTouchTypes` to limit scrolling to a particular set of touch types.
@property(nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer NS_AVAILABLE_IOS(5_0);
// `pinchGestureRecognizer` will return nil when zooming is disabled.
@property(nullable, nonatomic, readonly) UIPinchGestureRecognizer *pinchGestureRecognizer NS_AVAILABLE_IOS(5_0);
// `directionalPressGestureRecognizer` is disabled by default, but can be enabled to perform scrolling in response to up / down / left / right arrow button presses directly, instead of scrolling indirectly in response to focus updates.
@property(nonatomic, readonly) UIGestureRecognizer *directionalPressGestureRecognizer UIKIT_AVAILABLE_TVOS_ONLY(9_0);

@property(nonatomic) UIScrollViewKeyboardDismissMode keyboardDismissMode NS_AVAILABLE_IOS(7_0); // default is UIScrollViewKeyboardDismissModeNone

@property (nonatomic, strong, nullable) UIRefreshControl *refreshControl NS_AVAILABLE_IOS(10_0) __TVOS_PROHIBITED;

UIScrollViewDelegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 正在滚动时 scrollViewDidScroll
// any offset changes
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;

// 正在缩放时 scrollViewDidZoom
// any zoom scale changes
- (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2);

// 将要开始拖拽时(一次有效拖拽)手刚接触到scroll时 scrollViewWillBeginDragging
// called on start of dragging (may require some time and or distance to move)
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;

// called on finger up if the user dragged. velocity is in points/millisecond.
// targetContentOffset may be changed to adjust where the scroll view comes to rest
// 就要结束拖拽时 手将要离开scroll时 scrollViewWillEndDragging velocity当前的速度
// targetContentOffset此次滚动结束的目标偏远
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0);

// called on finger up if the user dragged. decelerate is true if it will continue moving afterwards
// 已经结束拖拽时 手刚离开scroll时 scrollViewDidEndDragging decelerate是否会继续向前滚动一段距离
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;

// called on finger up as we are moving
// 将要开始减速时 手刚离开时
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;

// called when scroll view grinds to a halt
// 已经结束减速时 停下来时
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;

// // called when setContentOffset/scrollRectVisible:animated: finishes. not called if not animating
// 已经滚动滚动(使用代码设置的滚动时)
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
// return a view that will be scaled. if delegate returns nil, nothing happens

// 返回需要缩放的view
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;

// called before the scroll view begins zooming its content
// 将要开始缩放
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view NS_AVAILABLE_IOS(3_2);

// scale between minimum and maximum. called after any 'bounce' animations
// 已结束缩放 view缩放的view scale缩放比例
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale;

// return a yes if you want to scroll to the top. if not defined, assumes YES
// 点击状态栏时是否可以返回顶部
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;

// called when scrolling animation finished. may be called immediately if already at top
// 点击状态栏后回到顶部结束
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;

iOS沙盒

沙盒目录

NSString *homeDirectory = NSHomeDirectory();

  • home/Documents

NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]

①存放内容 我们可以将应用程序的数据文件保存在该目录下。不过这些数据类型仅限于不可再生的数据,可再生的数据文件应该存放在Library/Cache目录下。

②会被iTunes同步

  • home/Library/Caches

NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]

①存放内容主要是缓存文件,用户使用过程中缓存都可以保存在这个目录中。前面说过,Documents目录用于保存不可再生的文件,那么这个目录就用于保存那些可再生的文件,比如网络请求的数据。鉴于此,应用程序通常还需要负责删除这些文件,注意:存储不足时,系统会清理此文件夹。

②不会被iTunes同步

  • home/Library/Preferences

    [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES)[0] stringByAppendingPathComponent:@"Preferences"];

    ①存放内容应用程序的偏好设置文件。我们使用NSUserDefaults写的设置数据都会保存到该目录下的一个plist文件中,这就是所谓的写道plist中!

    ②会被iTunes同步

  • home/tmp

NSTemporaryDirectory();

存储放临时文件,退出App后自动删除 ,可以任意操作此文件夹,不会被iTunes同步

  • home/MyApp.app

    [[NSBundle mainBundle] bundlePath]

    ①存放内容该目录包含了应用程序本身的数据,包括资源文件和可执行文件等。程序启动以后,会根据需要从该目录中动态加载代码或资源到内存,这里用到了lazy loading的思想。

    ②整个目录是只读的为了防止被篡改,应用在安装的时候会将该目录签名。非越狱情况下,该目录中内容是无法更改的;在越狱设备上如果更改了目录内容,对应的签名就会被改变,这种情况下苹果官网描述的后果是应用程序将无法启动,我没实践过。

    ③不会被iTunes同步

appearance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 设置 UINavigationBar 上的 item 的颜色,包括 item 和 backItem
[UINavigationBar appearance].tintColor= [UIColor redColor];
// 设置 UINavigationBar 的背景颜色
[UINavigationBar appearance].barTintColor = [UIColor blueColor];

// 设置 UINavigationBar 上的标题的属性
[UINavigationBar appearance].titleTextAttributes =
@{
NSFontAttributeName: [UIFont boldSystemFontOfSize:40],
NSForegroundColorAttributeName : [UIColor whiteColor],
};
// 设置 UITabBar 上的 item 的颜色
[UITabBar appearance].tintColor = [UIColor redColor];
// 设置 UITabBar 的背景颜色
[UITabBar appearance].barTintColor = [UIColor blueColor];

AFN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

{
// 请求格式化
manager.requestSerializer = [AFJSONRequestSerializer serializer];
}

{
// 响应
AFHTTPResponseSerializer *serializer=[AFHTTPResponseSerializer serializer];
serializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",
@"application/json",
@"text/json" ,
@"text/javascript",
@"video/mp4", nil];
manager.responseSerializer = serializer;
}

保存图片到相册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

- (void)loadImageFinished:(UIImage *)image
{
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), (__bridge void *)self);
}

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
[MEMEITUOKHUD dismiss];
if (error) {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"保存失败" message:error.domain preferredStyle:UIAlertControllerStyleAlert];
[alertVC addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]];
[self presentViewController:alertVC animated:YES completion:nil];
} else {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"保存成功" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertVC addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]];
[self presentViewController:alertVC animated:YES completion:nil];
}
}

显示模式 UIViewContentMode

1.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef NS_ENUM(NSInteger, UIViewContentMode) {

// 图片强行变形缩放显示满 UIImageView 为止
UIViewContentModeScaleToFill,

// 图片等比例缩放到完全显示到内部,而且保证最少有一边填满 UIImageView
UIViewContentModeScaleAspectFit,

// 图片等比例缩放到 `较短` 一边也填满 UIImageView 为止,多余的裁剪掉
UIViewContentModeScaleAspectFill,

// 这个不知道是什么鬼
// 调用 setNeedsDisplay 方法时,就会重新渲染图片
UIViewContentModeRedraw,


// 不拉伸(压缩)图片,取特定位置,裁剪图片,不变形
UIViewContentModeCenter,
//顶部
UIViewContentModeTop,
//底部
UIViewContentModeBottom,
//左边
UIViewContentModeLeft,
//右边
UIViewContentModeRight,
//左上
UIViewContentModeTopLeft,
//右上
UIViewContentModeTopRight,
//左下
UIViewContentModeBottomLeft,
//右下
UIViewContentModeBottomRight,
};

iOS开发的一些尺寸规范

iPhone尺寸规格

1
2
3
4
5
iPhone4/4S              320x480    
iPhone5/5C/5S 320x568
iPhone6/6S/7/8 375x667
iPhone6P/6SP/7P/8P 414x736
iPhone X 375x812

LaunchImage 启动图

  • 只适配 iphone 时的启动图如下
1
2
3
4
5
640x960.png
640x1136.png
750x1334.png
1125x2436.png
1242x2208.png

icon

https://icon.wuruihong.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
icon-20-ipad.png
icon-20@2x-ipad.png
icon-20@2x.png
icon-20@3x.png
icon-29-ipad.png
icon-29.png
icon-29@2x-ipad.png
icon-29@2x.png
icon-29@3x.png
icon-40.png
icon-40@2x.png
icon-40@3x.png
icon-50.png
icon-50@2x.png
icon-57.png
icon-57@2x.png
icon-60@2x.png
icon-60@3x.png
icon-72.png
icon-72@2x.png
icon-76.png
icon-76@2x.png
icon-83.5@2x.png
icon-1024.png

iOS系统权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!-- 保存图片到相册相册 -->   
<key> NSPhotoLibraryAddUsageDescription </key>
<string>为了把您涂鸦好的图片保存到相册,需要您提供授权</string>
<!-- 获取相册的图片 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>
<!-- 位置 -->
<key>NSLocationUsageDescription</key>
<string>App需要您的同意,才能访问位置</string>
<!-- 在使用期间访问位置 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>App需要您的同意,才能在使用期间访问位置</string>
<!-- 始终访问位置 -->
<key>NSLocationAlwaysUsageDescription</key>
<string>App需要您的同意,才能始终访问位置</string>
<!-- 日历 -->
<key>NSCalendarsUsageDescription</key>
<string>App需要您的同意,才能访问日历</string>
<!-- 提醒事项 -->
<key>NSRemindersUsageDescription</key>
<string>App需要您的同意,才能访问提醒事项</string>
<!-- 运动与健身 -->
<key>NSMotionUsageDescription</key>
<string>App需要您的同意,才能访问运动与健身</string>
<!-- 健康更新 -->
<key>NSHealthUpdateUsageDescription</key>
<string>App需要您的同意,才能访问健康更新 </string>
<!-- 健康分享 -->
<key>NSHealthShareUsageDescription</key>
<string>App需要您的同意,才能访问健康分享</string>
<!-- 蓝牙 -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>App需要您的同意,才能访问蓝牙</string>
<!-- 媒体资料库 -->
<key>NSAppleMusicUsageDescription</key>
<string>App需要您的同意,才能访问媒体资料库</string>
- END -
扫一扫上面的二维码图案,加我微信

文章作者:梁大红

特别声明:若无特殊声明均为原创,转载请注明,侵权请联系

版权声明:署名-非商业性使用-禁止演绎 4.0 国际