你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 解決3D Touch導致系統相冊崩潰的問題

解決3D Touch導致系統相冊崩潰的問題

編輯:IOS開發基礎

ChMkJlYKAYOIUE03AACOF3jrRsUAADIOwPbXlQAAI4v596.jpg

UIImagePickerController是iOS中自帶的系統相冊選擇器, 使用起來非常簡便.

UIImagePickerController

UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
// 設置sourceType為系統相冊, 如果使用Camera請對應修改該屬性.
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imagePickerController.allowsEditing = YES;
imagePickerController.delegate = self;
[self presentViewController:imagePickerController animated:YES completion:nil];

UIImagePickerController本身繼承自UINavigationController, 因此不能將其簡單視作一個UIViewController來看待. 

了UINavigationControllerDelegate和UIImagePickerControllerDelegate協議, 包含了由三個ViewController組成的導航視圖, 我們通過打印其viewControllers即可看出來:

(lldb) po [picker viewControllers]
[__NSArrayI 0x146b31140]( (因識別問題,此處用方括號代替尖括號)
    [PUUIAlbumListViewController: 0x14609a200],(因識別問題,此處用方括號代替尖括號)
    [PUUIPhotosAlbumViewController: 0x1458e5a00],(因識別問題,此處用方括號代替尖括號)
    [PUUIImageViewController: 0x1468f1ad0](因識別問題,此處用方括號代替尖括號)
)

UIImagePickerControllerDelegate

UIImagePickerControllerDelegate協議有如下三個代理方法.

__TVOS_PROHIBITED @protocol UIImagePickerControllerDelegate[NSObject] (因識別問題,此處用方括號代替尖括號)
@optional
// The picker does not dismiss itself; the client dismisses it in these callbacks.
// The delegate will receive one or the other, but not both, depending whether the user
// confirms or cancels.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary *)editingInfo NS_DEPRECATED_IOS(2_0, 3_0);
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info;
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;

其中info包含了所選取照片的基本信息, 如下:

// info dictionary keys
UIKIT_EXTERN NSString *const UIImagePickerControllerMediaType __TVOS_PROHIBITED;      // an NSString (UTI, i.e. kUTTypeImage)
UIKIT_EXTERN NSString *const UIImagePickerControllerOriginalImage __TVOS_PROHIBITED;  // a UIImage
UIKIT_EXTERN NSString *const UIImagePickerControllerEditedImage __TVOS_PROHIBITED;    // a UIImage
UIKIT_EXTERN NSString *const UIImagePickerControllerCropRect __TVOS_PROHIBITED;       // an NSValue (CGRect)
UIKIT_EXTERN NSString *const UIImagePickerControllerMediaURL __TVOS_PROHIBITED;       // an NSURL
UIKIT_EXTERN NSString *const UIImagePickerControllerReferenceURL        NS_AVAILABLE_IOS(4_1) __TVOS_PROHIBITED;  // an NSURL that references an asset in the AssetsLibrary framework
UIKIT_EXTERN NSString *const UIImagePickerControllerMediaMetadata       NS_AVAILABLE_IOS(4_1) __TVOS_PROHIBITED;  // an NSDictionary containing metadata from a captured photo
UIKIT_EXTERN NSString *const UIImagePickerControllerLivePhoto NS_AVAILABLE_IOS(9_1) __TVOS_PROHIBITED;  // a PHLivePhoto
}

當在系統相冊中選取了照片之後, 會調用imagePickerController:didFinishPickingMediaWithInfo:方法, 在其中可以添加對該照片的一些操作, 如寫入相冊等.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
    UIImage *editedImage = info[UIImagePickerControllerEditedImage];
    UIImage *savedImage = editedImage ?: originalImage;

    if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
        UIImageWriteToSavedPhotosAlbum(savedImage, nil, nil, nil);
    }

    __weak ViewController *weakSelf = self;
    [picker dismissViewControllerAnimated:YES completion:^{
        weakSelf.imageView.image = savedImage;
    }];
}

並且UIImagePickerController自身並不會dismiss, 需要我們自己調用, 並且通過回調的方式將照片進行相應的處理. 

上邊的代碼, 即在UIImagePickerController的dismiss操作之後, 將照片放置到UIImageView中.

自定義UIImagePickerController

創建一個UIViewController, 繼承自UIImagePickerController, 並實現相關協議方法, 即可以自定義一個相冊選擇器. 

不過實際上依然只是對UIImagePickerController進行了一次包裝而已. 

請參考demo: https://github.com/icetime17/Playground/tree/master/DemoUIImagePicker

另外, 可以使用PhotoKit框架, 結合UICollectionView來實現真正意義上的自定義相冊. 

參考博客: iOS--使用PhotoKit代替ALAssetsLibrary來管理相冊資源

問題

3D Touch

3D Touch是iPhone 6s/6splus設備才有的特點, 在系統相冊中長按一個照片, 可觸發3D Touch相關的操作. 

而在沒有3D Touch的設備中, 在系統相冊中長按一個照片, 會導致crash. 這看起來像是iOS系統的一個bug.

原因在於: 

觸發3D Touch操作後, PUPhotosGridViewController的previewingContext:viewControllerForLocation:未實現, 所以導致crash.

解決方法: 

  • 使用runtime來實現method swizzling, 即在runtime中將該方法替換. 

  • 使用method_exchangeImplementations(originalMethod, replacementMethod);方法即可實現.

首先, 封裝一個方法用於實現method swizzling

- (void)replaceSelectorForClass:(Class)cls
                  SelectorOriginal:(SEL)original
                   SelectorReplace:(SEL)replacement
                    withBlock:(id)block {

    IMP implementation = imp_implementationWithBlock(block);
    Method originalMethod = class_getInstanceMethod(cls, original);
    class_addMethod(cls, replacement, implementation, method_getTypeEncoding(originalMethod));
    Method replacementMethod = class_getInstanceMethod(cls, replacement);
    if (class_addMethod(cls, original, method_getImplementation(replacementMethod), method_getTypeEncoding(replacementMethod))) {
        class_replaceMethod(cls, replacement, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, replacementMethod);
    }
    
}

在AppDelegate的application:didFididFinishLaunchingWithOptions:方法中, 進行method swizzling:

- (void)preventImagePickerCrashOn3DTouch {
    // Load PhotosUI and bail if 3D Touch is unavailable.
    // (UIViewControllerPreviewing may be redundant,
    // as PUPhotosGridViewController only seems to exist on iOS 9,
    // but I'm being cautious.)
    NSString *photosUIPath = @"/System/Library/Frameworks/PhotosUI.framework";
    NSBundle *photosUI = [NSBundle bundleWithPath:photosUIPath];
    Class photosClass = [photosUI classNamed:@"PUPhotosGridViewController"];
    if (!(photosClass && objc_getProtocol("UIViewControllerPreviewing"))) {
        return;
    }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"

    SEL selector = @selector(ab_previewingContext:viewControllerForLocation:);
    [self replaceSelectorForClass:photosClass
                 SelectorOriginal:@selector(previewingContext:viewControllerForLocation:)
                  SelectorReplace:selector
                        withBlock:^UIViewController *(id self, id previewingContext, CGPoint location) {

        // Default implementation throws on iOS 9.0 and 9.1.
        @try {
            MTLog(@"Replace method at runtime to prevent UIImagePicker crash on 3D Touch.");
            return ((UIViewController *(*)(id, SEL, id, CGPoint))objc_msgSend)(self, selector, previewingContext, location);
        } @catch (NSException *e) {
            return nil;
        }
    }];

#pragma clang diagnostic pop
}

這樣, 即可成功解決3D Touch導致系統的UIImagePickerController崩潰的問題。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved