PHAsset Preview Widget

This type of blogs is just for myself, noting what ever troubled me once or more than one time.

PHAsset Preview Widget

I ever made a widget like the one that is used to preview selected images on Wechat moments.

There are some aspects to be considered.

Page Controller

Need to use UIPageController to manipulate to switches of selected asstes.

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

#pragma mark - Init
- (instancetype)initWithAssets:(NSMutableArray *)collection {
self = [super initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
if (self) {
_assets = collection;
_currentIndex = 0;
_statusBarHidden = YES;
_deletedAssets = [NSMutableSet set];
_sizeArray = [NSMutableArray array];
for (NSInteger i = 0; i < self.assets.count; i++) {
_sizeArray[i] = [NSNull null];
}
}
return self;
}

#pragma mark - UIPageViewControllerDataSource

- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:[AZHAssetsPhotoViewController class]]) {
AZHAssetsPhotoViewController *photoViewController = (AZHAssetsPhotoViewController *) viewController;
return [self photoViewControllerWithIndex:photoViewController.index - 1];
}
return nil;
}

- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:[AZHAssetsPhotoViewController class]]) {
AZHAssetsPhotoViewController *photoViewController = (AZHAssetsPhotoViewController *) viewController;
return [self photoViewControllerWithIndex:photoViewController.index + 1];
}

return nil;
}

#pragma mark - UIPageViewControllerDelegate

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
if (finished) {
self.sizeLabel.text = @"";
AZHAssetsPhotoViewController *photoViewController = [self currentViewController];
NSInteger currentIndex = photoViewController.index;
self.currentIndex = currentIndex;

PHAsset *asset = self.assets[currentIndex];
BOOL isDeleted = [self.deletedAssets containsObject: asset];
[self.pickerIndicator setSelected:!isDeleted animated:NO];

if (self.sizeArray[currentIndex] != [NSNull null]) {
NSNumber *size = self.sizeArray[currentIndex];
[self configureSizeLabelWithSize:[size unsignedIntegerValue] forIndex:currentIndex];
}
}
}

Gesture Handler

Need to handle both tap gesture and double tap gesture.

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
@property (nonatomic, strong) UITapGestureRecognizer *singleTap;
@property (nonatomic, strong) UITapGestureRecognizer *doubleTap;


#pragma mark Action

- (void)initGestures {
self.singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapAction)];
self.singleTap.delegate = self;
[self.view addGestureRecognizer:self.singleTap];

self.doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapAction:)];
self.doubleTap.numberOfTapsRequired = 2;
self.doubleTap.delegate = self;
[self.view addGestureRecognizer:self.doubleTap];
}

- (void)singleTapAction {
[self performSelector:@selector(singleTapActionWithAnimation) withObject:nil afterDelay:0.15];
}

- (void)singleTapActionWithAnimation {
[UIView animateWithDuration:0.1
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^{
CGRect topFrame = self.headerBar.frame;
CGRect bottomFrame = self.bottomBar.frame;
if (topFrame.origin.y < 0) {
topFrame.origin.y = topFrame.origin.y + topFrame.size.height;
bottomFrame.origin.y = bottomFrame.origin.y - bottomFrame.size.height;
} else {
topFrame.origin.y = topFrame.origin.y - topFrame.size.height;
bottomFrame.origin.y = bottomFrame.origin.y + bottomFrame.size.height;
}

self.headerBar.frame = topFrame;
self.bottomBar.frame = bottomFrame;
} completion:nil];
}

- (void)doubleTapAction:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(singleTapActionWithAnimation) object:nil];
[[self currentViewController] runDoubleTapAction];
}
}

Update Labels Indicating Downloading (Or Size) And The Number Of Count

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
@property (nonatomic, strong) UILabel *countLabel;
@property (nonatomic, strong) UILabel *sizeLabel;


- (void)updateCountLabel {
NSUInteger count = self.assets.count - self.deletedAssets.count;
self.doneButton.enabled = (count > 0);

if (count > 0) {
self.countLabel.text = [NSString stringWithFormat:@"(%@ / %@)", [NSNumber numberWithUnsignedInteger:self.currentIndex+1], [NSNumber numberWithUnsignedInteger:self.assets.count]];
} else {
self.countLabel.text = nil;
}
}

- (void)configureSizeLabelWithDownloadingProgress:(double)progress forIndex:(NSInteger)index {
if (index == self.currentIndex) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterPercentStyle;
formatter.usesSignificantDigits = YES;
formatter.maximumSignificantDigits = 3;
NSString *progressString = [formatter stringFromNumber:[NSNumber numberWithDouble:progress]];
self.sizeLabel.text = [[AZHStrings strings] downloading:progressString];
[self updateSizeLabel];
[self updateCountLabel];
}
}

- (void)configureSizeLabelWithSize:(NSUInteger)size forIndex:(NSInteger)index {
if (index == self.currentIndex) {
NSString *sizeString = [NSByteCountFormatter stringFromByteCount:size countStyle:NSByteCountFormatterCountStyleFile];
self.sizeLabel.text = [[AZHStrings strings] keySize:sizeString];
[self updateSizeLabel];
[self updateCountLabel];
}
}

- (void)updateSizeLabel {
dispatch_async(dispatch_get_main_queue(), ^{
CGSize requiredSize = self.sizeLabel.intrinsicContentSize;
CGRect frame = self.sizeLabel.frame;
frame.size = CGSizeMake(requiredSize.width, frame.size.height);
self.sizeLabel.frame = frame;
});
}

Moreover, need a delegate to update the labels.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma mark - AZHAssetsPhotoViewControllerDelegate
- (void)photoViewController:(AZHAssetsPhotoViewController *)photoVC didLoadSize:(NSUInteger)size {
self.sizeArray[photoVC.index] = @(size);
if (photoVC == [self currentViewController]) {
[self configureSizeLabelWithSize:size forIndex:photoVC.index];
}
}

- (void)photoViewController:(AZHAssetsPhotoViewController *)photoVC updateDownloadingProgress:(double)progress {
AZHAssetsPhotoViewController *currentVC = [self currentViewController];
if (photoVC == currentVC) {
[self configureSizeLabelWithDownloadingProgress:progress forIndex:photoVC.index];
}
}

Handle iCloud Syncing

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
- (void)loadAsset {
PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init];
[imageOptions setNetworkAccessAllowed:YES];
[self requestAssetImageWithOptions:imageOptions];

if (self.asset.mediaType == PHAssetMediaTypeImage) {

imageOptions.progressHandler=^(double progress, NSError *error, BOOL *stop, NSDictionary *info){
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:updateDownloadingProgress:)]) {
[self.delegate photoViewController:self updateDownloadingProgress:progress];
}
});
}
};

[[PHImageManager defaultManager] requestImageDataForAsset:self.asset options:imageOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
if (imageData) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:didLoadSize:)]) {
[self.delegate photoViewController:self didLoadSize:[imageData length]];
}
});
}
}];
} else if (self.asset.mediaType == PHAssetMediaTypeVideo) {

PHVideoRequestOptions *videoOptions = [[PHVideoRequestOptions alloc] init];
videoOptions.networkAccessAllowed = YES;
videoOptions.progressHandler=^(double progress, NSError *error, BOOL *stop, NSDictionary *info){
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:updateDownloadingProgress:)]) {
[self.delegate photoViewController:self updateDownloadingProgress:progress];
}
});
}
};

[[PHImageManager defaultManager] requestAVAssetForVideo:self.asset options:videoOptions resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
self.player = player;

if (asset && [asset isKindOfClass:[AVURLAsset class]]) {
AVURLAsset *urlAsset = (AVURLAsset*)asset;
NSNumber *videoSize;
[urlAsset.URL getResourceValue:&videoSize forKey:NSURLFileSizeKey error:nil];

dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:didLoadSize:)]) {
[self.delegate photoViewController:self didLoadSize:[videoSize unsignedIntegerValue]];
}
});
}
}];
}
}

// for handling the thumbnail
- (void)requestAssetImageWithOptions:(PHImageRequestOptions *)options {
[[PHImageManager defaultManager] requestImageForAsset:self.asset targetSize:self.imageView.frame.size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
if (result) {
self.imageView.image = result;
}
}];
}

Sacle Photos

Need to customize a scroll view to handle the scale of photos.

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
// AZHScrollView.h

@interface AZHScrollView : UIScrollView

@property (nonatomic, weak) UIViewController *viewController;

@end


// AZHScollView.m
#import "AZHScrollView.h"

@implementation AZHScrollView

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.viewController != nil && [self.viewController respondsToSelector:@selector(touchesBegan:withEvent:)])
[self.viewController performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event];

[super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.viewController != nil && [self.viewController respondsToSelector:@selector(touchesEnded:withEvent:)])
[self.viewController performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event];

[super touchesEnded:touches withEvent:event];
}

@end


// AZHAssetsPhotoViewController

@property (nonatomic, strong) AZHScrollView *scrollView;

// in the init view function:

self.scrollView = [[AZHScrollView alloc] initWithFrame:self.view.bounds];
[self.scrollView setBackgroundColor:[UIColor blackColor]];

[self.scrollView setViewController:self];
[self.scrollView setMaximumZoomScale:3.0f];
[self.scrollView setMinimumZoomScale:1.0f];
[self.scrollView setClipsToBounds:YES];
[self.scrollView setScrollEnabled:YES];
[self.scrollView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[self.scrollView setDelegate:self];
[self.view addSubview:self.scrollView];

- (void)doubleTap {
if (self.scrollView.zoomScale != 1.0f)
[self.scrollView setZoomScale:1.0f animated:YES];
else
[self.scrollView setZoomScale:3.0f animated:YES];
}

#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = self.imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;

if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
CGFloat tempx = self.imageView.center.x - ( scrollerBounds.size.width / 2 );
CGFloat tempy = self.imageView.center.y - ( scrollerBounds.size.height / 2 );
CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);

pScrollView.contentOffset = myScrollViewOffset;
}

UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
anEdgeInset.right = -anEdgeInset.left;
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
anEdgeInset.bottom = -anEdgeInset.top;
}
pScrollView.contentInset = anEdgeInset;
}

Source Codes

AZHPickerIndicator

.h

1
2
3
4
5
6
7
@interface AZHPickerIndicator : UIControl

- (instancetype)initWithFrame:(CGRect)frame selected:(BOOL)selected;
- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color selected:(BOOL)selected NS_DESIGNATED_INITIALIZER;

- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
@end

.m

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
127
128
129
130
#import "AZHPickerIndicator.h"
#import "AZHResources+IconFont.h"

@interface AZHPickerIndicator ()
@property (nonatomic, strong) UIImageView *checkmark;
@property (nonatomic, strong) UIColor *selectedColor;
@property (nonatomic, strong) UIColor *unselectedColor;
@property (nonatomic, strong) UIView *overlayView;
@end

@implementation AZHPickerIndicator

- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color selected:(BOOL)selected {
self = [super initWithFrame:frame];
if (self) {
_selectedColor = color;
_unselectedColor = [UIColor colorWithRed:153.0f/255.0f
green:153.0f/255.0f
blue:153.0f/255.0f
alpha:0.5];

_checkmark = [[UIImageView alloc] init];
_checkmark.image = [AZHResources imageTemplate:@"checkmark"];
_checkmark.tintColor = [SFTheme currentTheme].whiteColor;
_checkmark.userInteractionEnabled = NO;

_overlayView = [[UIView alloc] initWithFrame:self.bounds];
_overlayView.layer.cornerRadius = frame.size.width / 2;
_overlayView.backgroundColor = _unselectedColor;
_overlayView.userInteractionEnabled = NO;

[self addSubview:_overlayView];
[_overlayView addSubview:_checkmark];

[self addInitialConstraints];

[self setSelected:selected animated:NO];
}
return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame color:[SFTheme currentTheme].brandBlueColor];
}

- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color {
return [self initWithFrame:frame color:color selected:NO];
}

- (instancetype)initWithFrame:(CGRect)frame selected:(BOOL)selected {
return [self initWithFrame:frame color:[SFTheme currentTheme].brandBlueColor selected:selected];
}

- (void)addInitialConstraints {
NSDictionary *views = @{@"view": self,
@"overlayView": self.overlayView,
@"checkmark": self.checkmark};
// overlay view
self.overlayView.translatesAutoresizingMaskIntoConstraints = NO;
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[overlayView]-0-|"
options:NSLayoutFormatAlignAllBottom
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[overlayView]-0-|"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:views]];

// checkmark
self.checkmark.translatesAutoresizingMaskIntoConstraints = NO;
[self.overlayView addConstraint:[NSLayoutConstraint constraintWithItem:self.checkmark
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.overlayView
attribute:NSLayoutAttributeWidth
multiplier:0.5f
constant:0.0f]];

[self.overlayView addConstraint:[NSLayoutConstraint constraintWithItem:self.checkmark
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.overlayView
attribute:NSLayoutAttributeHeight
multiplier:0.5f
constant:0.0f]];

[self.overlayView addConstraint:[NSLayoutConstraint constraintWithItem:self.checkmark
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.overlayView
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]];

[self.overlayView addConstraint:[NSLayoutConstraint constraintWithItem:self.checkmark
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.overlayView
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f]];
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
self.selected = selected;
if (selected) {
self.overlayView.backgroundColor = self.selectedColor;
}
else {
self.overlayView.backgroundColor = self.unselectedColor;
}

if (animated) {
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.overlayView.layer setValue:@1.15 forKeyPath:@"transform.scale"];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.overlayView.layer setValue:@0.92 forKeyPath:@"transform.scale"];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.08 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.overlayView.layer setValue:@1.0 forKeyPath:@"transform.scale"];
} completion:^(BOOL finished) {

}];
}];
}];
}
}

@end

AZHScrollView

.h

1
2
3
4
5
@interface AZHScrollView : UIScrollView

@property (nonatomic, weak) UIViewController *viewController;

@end

.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "AZHScrollView.h"

@implementation AZHScrollView

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.viewController != nil && [self.viewController respondsToSelector:@selector(touchesBegan:withEvent:)])
[self.viewController performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event];

[super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.viewController != nil && [self.viewController respondsToSelector:@selector(touchesEnded:withEvent:)])
[self.viewController performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event];

[super touchesEnded:touches withEvent:event];
}

@end

AZHAssetsPhotoViewController

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "AZHAssetsPhotoViewController.h"

@class PHAsset;
@class AZHAssetsPreviewController;

@protocol AZHAssetsPreviewControllerDelegate <NSObject>

- (void)previewController:(AZHAssetsPreviewController *)previewVC didSelectAsset:(PHAsset *)asset;
- (void)previewController:(AZHAssetsPreviewController *)previewVC didUnselectAsset:(PHAsset *)asset;
- (void)previewControllerDidFinish:(AZHAssetsPreviewController *)previewVC;

@end

@interface AZHAssetsPreviewController : UIPageViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIGestureRecognizerDelegate, AZHAssetsPhotoViewControllerDelegate>
- (instancetype)initWithAssets:(NSArray *)collection;
@property (nonatomic, weak) id <AZHAssetsPreviewControllerDelegate> previewDelegate;
@end

.m

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
#import "AZHAssetsPreviewController.h"
#import "AZHPickerIndicator.h"
#import <Photos/PHAsset.h>

@interface AZHAssetsPreviewController ()
@property (nonatomic, strong) NSArray *assets;
@property (nonatomic) NSInteger currentIndex;
@property (nonatomic, strong) NSMutableSet *deletedAssets;
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UIView *bottomBar;
@property (nonatomic, strong) UIBarButtonItem *doneButton;
@property (nonatomic, strong) UILabel *countLabel;
@property (nonatomic, strong) UILabel *sizeLabel;
@property (nonatomic, strong) AZHPickerIndicator *pickerIndicator;

@property (nonatomic, strong) UITapGestureRecognizer *singleTap;
@property (nonatomic, strong) UITapGestureRecognizer *doubleTap;
@property (nonatomic) BOOL statusBarHidden;

@property (nonatomic, strong) NSMutableArray *sizeArray;
@end

@implementation AZHAssetsPreviewController

- (instancetype)initWithAssets:(NSMutableArray *)collection {
self = [super initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
if (self) {
_assets = collection;
_currentIndex = 0;
_statusBarHidden = YES;
_deletedAssets = [NSMutableSet set];
_sizeArray = [NSMutableArray array];
for (NSInteger i = 0; i < self.assets.count; i++) {
_sizeArray[i] = [NSNull null];
}
}
return self;
}

- (void)viewDidLoad {
[super viewDidLoad];
self.dataSource = self;
self.delegate = self;
self.view.backgroundColor = [UIColor blackColor];

[self initHeaderView];
[self initBottomView];
[self initGestures];


if (self.assets.count > 0) {
AZHAssetsPhotoViewController *firstPhotoVC = [self photoViewControllerWithIndex:0];
[self setViewControllers:[NSArray arrayWithObject:firstPhotoVC]
direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];

[self setNavigationBar:YES];
}

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];

[self setNavigationBar:NO];
}

#pragma mark View

- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return YES;
}

- (BOOL)prefersStatusBarHidden {
return self.statusBarHidden;
}

- (void)setNavigationBar:(BOOL)hidden {
[self.navigationController setNavigationBarHidden:hidden animated:YES];
[self.navigationController setToolbarHidden:hidden];
self.statusBarHidden = hidden;
[self setNeedsStatusBarAppearanceUpdate];
}

- (void)initHeaderView {
UIToolbar *overlay = [self makeToolbarWithOption:YES];

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage: [AZHResources imageNamed:@"back-indicator"]
style:UIBarButtonItemStylePlain
target:self
action:@selector(barBackButtonClicked)];
backButton.tintColor = [SFTheme currentTheme].whiteColor;

UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];

AZHPickerIndicator *picker = [[AZHPickerIndicator alloc] initWithFrame:CGRectMake(0, 0, 24, 24) selected:YES];
[picker addTarget:self action:@selector(handleClickOnPicker:) forControlEvents:UIControlEventTouchUpInside];

picker.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:picker
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:overlay
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:picker
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:overlay
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:-10];

overlay.items = @[backButton, flexibleSpace];

[overlay addSubview:picker];
[overlay addConstraints:@[centerY, trailing]];

self.pickerIndicator = picker;
self.headerBar = overlay;
}

- (void)initBottomView {
UIToolbar *overlay = [self makeToolbarWithOption:NO];
UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
UIBarButtonItem* doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(doneButtonClicked)];
UILabel *countLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 80, 40)];
countLabel.textAlignment = NSTextAlignmentRight;
countLabel.textColor = [SFTheme currentTheme].whiteColor;
UIBarButtonItem *countLabelItem = [[UIBarButtonItem alloc] initWithCustomView:countLabel];

UILabel *sizeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
sizeLabel.textAlignment = NSTextAlignmentLeft;
sizeLabel.textColor = [SFTheme currentTheme].whiteColor;
UIBarButtonItem *sizeLabelItem = [[UIBarButtonItem alloc] initWithCustomView:sizeLabel];


overlay.items = @[sizeLabelItem, flexibleSpace, countLabelItem, doneButton];

self.sizeLabel = sizeLabel;
self.countLabel = countLabel;
self.doneButton = doneButton;
self.bottomBar = overlay;

[self updateCountLabel];
}

- (void)updateCountLabel {
NSUInteger count = self.assets.count - self.deletedAssets.count;
self.doneButton.enabled = (count > 0);

if (count > 0) {
self.countLabel.text = [NSString stringWithFormat:@"(%@ / %@)", [NSNumber numberWithUnsignedInteger:self.currentIndex+1], [NSNumber numberWithUnsignedInteger:self.assets.count]];
} else {
self.countLabel.text = nil;
}
}

- (UIToolbar*)makeToolbarWithOption:(BOOL)isheader {
UIToolbar *overlay = [[UIToolbar alloc] initWithFrame:CGRectZero];

NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:overlay
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:overlay
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:overlay
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:overlay
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0];

CGFloat heightValue = isheader ? 64.0 : 44.0;
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:overlay
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:heightValue];

overlay.translatesAutoresizingMaskIntoConstraints = NO;
overlay.barStyle = UIBarStyleBlackOpaque;

[self.view addSubview:overlay];
[self.view addConstraints:@[leading, trailing, height]];
if (isheader) {
[self.view addConstraint:top];
} else {
[self.view addConstraint:bottom];
}

return overlay;
}

#pragma mark Action

- (void)initGestures {
self.singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapAction)];
self.singleTap.delegate = self;
[self.view addGestureRecognizer:self.singleTap];

self.doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapAction:)];
self.doubleTap.numberOfTapsRequired = 2;
self.doubleTap.delegate = self;
[self.view addGestureRecognizer:self.doubleTap];
}

- (void)barBackButtonClicked {
[self.navigationController popViewControllerAnimated:YES];
}

- (void)doneButtonClicked {
[self.navigationController popViewControllerAnimated:NO];
[self.previewDelegate previewControllerDidFinish:self];
}

- (void)handleClickOnPicker:(AZHPickerIndicator *)sender {
[self togglePickerIndicator];
}

- (void)togglePickerIndicator {
BOOL selected = self.pickerIndicator.selected;
[self.pickerIndicator setSelected:!selected animated:YES];

PHAsset *asset = self.assets[self.currentIndex];
if (self.pickerIndicator.selected) {
[self.deletedAssets removeObject:asset];
[self.previewDelegate previewController:self didSelectAsset:self.assets[self.currentIndex]];
}
else {
[self.deletedAssets addObject:asset];
[self.previewDelegate previewController:self didUnselectAsset:self.assets[self.currentIndex]];
}
}

- (void)singleTapAction {
[self performSelector:@selector(singleTapActionWithAnimation) withObject:nil afterDelay:0.15];
}

- (void)singleTapActionWithAnimation {
[UIView animateWithDuration:0.1
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^{
CGRect topFrame = self.headerBar.frame;
CGRect bottomFrame = self.bottomBar.frame;
if (topFrame.origin.y < 0) {
topFrame.origin.y = topFrame.origin.y + topFrame.size.height;
bottomFrame.origin.y = bottomFrame.origin.y - bottomFrame.size.height;
} else {
topFrame.origin.y = topFrame.origin.y - topFrame.size.height;
bottomFrame.origin.y = bottomFrame.origin.y + bottomFrame.size.height;
}

self.headerBar.frame = topFrame;
self.bottomBar.frame = bottomFrame;
} completion:nil];
}

- (void)doubleTapAction:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(singleTapActionWithAnimation) object:nil];
[[self currentViewController] runDoubleTapAction];
}
}

#pragma mark Size Label

- (void)configureSizeLabelWithDownloadingProgress:(double)progress forIndex:(NSInteger)index {
if (index == self.currentIndex) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterPercentStyle;
formatter.usesSignificantDigits = YES;
formatter.maximumSignificantDigits = 3;
NSString *progressString = [formatter stringFromNumber:[NSNumber numberWithDouble:progress]];
self.sizeLabel.text = [[AZHStrings strings] downloading:progressString];
[self updateSizeLabel];
[self updateCountLabel];
}
}

- (void)configureSizeLabelWithSize:(NSUInteger)size forIndex:(NSInteger)index {
if (index == self.currentIndex) {
NSString *sizeString = [NSByteCountFormatter stringFromByteCount:size countStyle:NSByteCountFormatterCountStyleFile];
self.sizeLabel.text = [[AZHStrings strings] keySize:sizeString];
[self updateSizeLabel];
[self updateCountLabel];
}
}

- (void)updateSizeLabel {
dispatch_async(dispatch_get_main_queue(), ^{
CGSize requiredSize = self.sizeLabel.intrinsicContentSize;
CGRect frame = self.sizeLabel.frame;
frame.size = CGSizeMake(requiredSize.width, frame.size.height);
self.sizeLabel.frame = frame;
});
}

#pragma mark - AZHAssetsPhotoViewControllerDelegate
- (void)photoViewController:(AZHAssetsPhotoViewController *)photoVC didLoadSize:(NSUInteger)size {
self.sizeArray[photoVC.index] = @(size);
if (photoVC == [self currentViewController]) {
[self configureSizeLabelWithSize:size forIndex:photoVC.index];
}
}

- (void)photoViewController:(AZHAssetsPhotoViewController *)photoVC updateDownloadingProgress:(double)progress {
AZHAssetsPhotoViewController *currentVC = [self currentViewController];
if (photoVC == currentVC) {
[self configureSizeLabelWithDownloadingProgress:progress forIndex:photoVC.index];
}
}

#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
CGPoint gestureLocation = [gestureRecognizer locationInView:self.view];
CGRect topBarFrame = self.headerBar.frame;
CGRect bottomBarFrame = self.bottomBar.frame;

if (CGRectContainsPoint(topBarFrame, gestureLocation)) { return NO; }
if (CGRectContainsPoint(bottomBarFrame, gestureLocation)) { return NO; }

return YES;
}

#pragma mark - Utils

- (AZHAssetsPhotoViewController *)photoViewControllerWithIndex:(NSInteger) index {
if (index == NSNotFound || index < 0 || index >= self.assets.count) {
return nil;
}

//The photo could use too much memory, must create VC only when needed.
PHAsset *asset = self.assets[index];
AZHAssetsPhotoViewController *photoViewController = [[AZHAssetsPhotoViewController alloc] initWithAsset:asset];
photoViewController.delegate = self;
photoViewController.index = index;

return photoViewController;
}

- (AZHAssetsPhotoViewController *)currentViewController {
if ([self.viewControllers.lastObject isKindOfClass:[AZHAssetsPhotoViewController class]]) {
AZHAssetsPhotoViewController *currentVC = (AZHAssetsPhotoViewController *) self.viewControllers.lastObject;
return currentVC;
}
return nil;
}

#pragma mark - UIPageViewControllerDataSource

- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:[AZHAssetsPhotoViewController class]]) {
AZHAssetsPhotoViewController *photoViewController = (AZHAssetsPhotoViewController *) viewController;
return [self photoViewControllerWithIndex:photoViewController.index - 1];
}
return nil;
}

- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:[AZHAssetsPhotoViewController class]]) {
AZHAssetsPhotoViewController *photoViewController = (AZHAssetsPhotoViewController *) viewController;
return [self photoViewControllerWithIndex:photoViewController.index + 1];
}

return nil;
}

#pragma mark - UIPageViewControllerDelegate

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
if (finished) {
self.sizeLabel.text = @"";
AZHAssetsPhotoViewController *photoViewController = [self currentViewController];
NSInteger currentIndex = photoViewController.index;
self.currentIndex = currentIndex;

PHAsset *asset = self.assets[currentIndex];
BOOL isDeleted = [self.deletedAssets containsObject: asset];
[self.pickerIndicator setSelected:!isDeleted animated:NO];

if (self.sizeArray[currentIndex] != [NSNull null]) {
NSNumber *size = self.sizeArray[currentIndex];
[self configureSizeLabelWithSize:[size unsignedIntegerValue] forIndex:currentIndex];
}
}
}

@end

AZHAssetsPhotoViewController

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@class PHAsset;
@class AZHAssetsPhotoViewController;

@protocol AZHAssetsPhotoViewControllerDelegate <NSObject>

- (void)photoViewController:(AZHAssetsPhotoViewController *)photoVC didLoadSize:(NSUInteger)size;
- (void)photoViewController:(AZHAssetsPhotoViewController *)photoVC updateDownloadingProgress:(double)progress;

@end

@interface AZHAssetsPhotoViewController : UIViewController <UIScrollViewDelegate>
@property (nonatomic) NSInteger index;
- (instancetype)initWithAsset:(PHAsset *)asset;
@property (nonatomic, weak) id<AZHAssetsPhotoViewControllerDelegate> delegate;

- (void)runDoubleTapAction;
@end

.m

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#import "AZHAssetsPhotoViewController.h"
#import "AZHScrollView.h"
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>

@interface AZHAssetsPhotoViewController ()
@property (nonatomic, strong) AZHScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) PHAsset *asset;

@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) UIButton *videoPlayButton;

@end

@implementation AZHAssetsPhotoViewController

- (instancetype)initWithAsset:(PHAsset *)asset {
self = [super init];
if (self) {
_asset = asset;
_imageView = [[UIImageView alloc] init];
if (asset.mediaType == PHAssetMediaTypeVideo) {
_videoPlayButton = [[UIButton alloc] initWithFrame:CGRectZero];
}
}
return self;
}

- (void)viewDidLoad {
[super viewDidLoad];
[self initView];
[self loadAsset];
}

- (void)loadAsset {
PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init];
[imageOptions setNetworkAccessAllowed:YES];
[self requestAssetImageWithOptions:imageOptions];

if (self.asset.mediaType == PHAssetMediaTypeImage) {

imageOptions.progressHandler=^(double progress, NSError *error, BOOL *stop, NSDictionary *info){
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:updateDownloadingProgress:)]) {
[self.delegate photoViewController:self updateDownloadingProgress:progress];
}
});
}
};

[[PHImageManager defaultManager] requestImageDataForAsset:self.asset options:imageOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
if (imageData) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:didLoadSize:)]) {
[self.delegate photoViewController:self didLoadSize:[imageData length]];
}
});
}
}];
} else if (self.asset.mediaType == PHAssetMediaTypeVideo) {

PHVideoRequestOptions *videoOptions = [[PHVideoRequestOptions alloc] init];
videoOptions.networkAccessAllowed = YES;
videoOptions.progressHandler=^(double progress, NSError *error, BOOL *stop, NSDictionary *info){
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:updateDownloadingProgress:)]) {
[self.delegate photoViewController:self updateDownloadingProgress:progress];
}
});
}
};

[[PHImageManager defaultManager] requestAVAssetForVideo:self.asset options:videoOptions resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
self.player = player;

if (asset && [asset isKindOfClass:[AVURLAsset class]]) {
AVURLAsset *urlAsset = (AVURLAsset*)asset;
NSNumber *videoSize;
[urlAsset.URL getResourceValue:&videoSize forKey:NSURLFileSizeKey error:nil];

dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(photoViewController:didLoadSize:)]) {
[self.delegate photoViewController:self didLoadSize:[videoSize unsignedIntegerValue]];
}
});
}
}];
}
}

- (void)requestAssetImageWithOptions:(PHImageRequestOptions *)options {
[[PHImageManager defaultManager] requestImageForAsset:self.asset targetSize:self.imageView.frame.size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
if (result) {
self.imageView.image = result;
}
}];
}

- (void)initView {
self.scrollView = [[AZHScrollView alloc] initWithFrame:self.view.bounds];
[self.scrollView setBackgroundColor:[UIColor blackColor]];

[self.scrollView setViewController:self];
[self.scrollView setMaximumZoomScale:3.0f];
[self.scrollView setMinimumZoomScale:1.0f];
[self.scrollView setClipsToBounds:YES];
[self.scrollView setScrollEnabled:YES];
[self.scrollView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[self.scrollView setDelegate:self];
[self.view addSubview:self.scrollView];

self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.imageView setContentMode:UIViewContentModeScaleAspectFit];
[self.imageView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[self.scrollView addSubview:self.imageView];

if (self.asset.mediaType == PHAssetMediaTypeVideo) {
[self.scrollView setMaximumZoomScale:1.0f];
[self.scrollView setMinimumZoomScale:1.0f];
[self.videoPlayButton setImage:[AZHResources imageNamed:@"play_video_white_withbg"] forState:UIControlStateNormal];
[self.scrollView addSubview:self.videoPlayButton];
self.videoPlayButton.translatesAutoresizingMaskIntoConstraints = NO;

[self.videoPlayButton addConstraint:[NSLayoutConstraint constraintWithItem:self.videoPlayButton
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:nil
multiplier:1.0
constant:50]];
[self.videoPlayButton addConstraint:[NSLayoutConstraint constraintWithItem:self.videoPlayButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.videoPlayButton
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.videoPlayButton
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.videoPlayButton
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f]];

[self.videoPlayButton addTarget:self action:@selector(playVideo) forControlEvents:UIControlEventTouchUpInside];
}
}

#pragma mark - Helper
- (void)runDoubleTapAction {
[self doubleTap];
}

- (void)doubleTap {
if (self.scrollView.zoomScale != 1.0f)
[self.scrollView setZoomScale:1.0f animated:YES];
else
[self.scrollView setZoomScale:3.0f animated:YES];
}

- (void)playVideo {
if (self.player) {
AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init];
playerViewController.player = self.player;
[self presentViewController:playerViewController animated:NO completion:^{
[self.player play];
}];
}
}

#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = self.imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;

if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
CGFloat tempx = self.imageView.center.x - ( scrollerBounds.size.width / 2 );
CGFloat tempy = self.imageView.center.y - ( scrollerBounds.size.height / 2 );
CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);

pScrollView.contentOffset = myScrollViewOffset;
}

UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
anEdgeInset.right = -anEdgeInset.left;
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
anEdgeInset.bottom = -anEdgeInset.top;
}
pScrollView.contentInset = anEdgeInset;
}

@end
文章目录
  1. 1. PHAsset Preview Widget
    1. 1.1. Page Controller
    2. 1.2. Gesture Handler
    3. 1.3. Update Labels Indicating Downloading (Or Size) And The Number Of Count
    4. 1.4. Handle iCloud Syncing
    5. 1.5. Sacle Photos
    6. 1.6. Source Codes
      1. 1.6.1. AZHPickerIndicator
        1. 1.6.1.1. .h
        2. 1.6.1.2. .m
      2. 1.6.2. AZHScrollView
        1. 1.6.2.1. .h
        2. 1.6.2.2. .m
      3. 1.6.3. AZHAssetsPhotoViewController
        1. 1.6.3.1. .h
        2. 1.6.3.2. .m
      4. 1.6.4. AZHAssetsPhotoViewController
        1. 1.6.4.1. .h
        2. 1.6.4.2. .m
,