Измените modalPresentationStyle на iOS13 сразу на всех экземплярах UIViewController, используя метод swizzling

11

[Q & A] Можно ли UIViewController.modalPresentationStyleглобально изменить значение на iOS 13, чтобы оно работало так же, как на iOS 12 (или ранее)?


Почему?

В IOS 13 SDK значение по умолчанию UIViewController.modalPresentationStyleсобственности было изменено UIModalPresentationFullScreenна UIModalPresentationAutomaticкоторый, насколько я знаю, решил UIModalPresentationPageSheetна устройствах IOS или по крайней мере на iPhone'ов.

Так как проект, над которым я работал несколько лет, стал довольно большим, есть десятки мест, где представлен контроллер представления. Новый стиль представления не всегда соответствует дизайну наших приложений, а иногда и приводит к тому, что пользовательский интерфейс разваливается. Именно поэтому мы решили UIViewController.modalPresentationStyleвернуться к UIModalPresentationFullScreenпрежней версии SDK для iOS13.

Но добавление viewController.modalPresentationStyle = UIModalPresentationFullScreenперед вызовом presentViewController:animated:completion:в каждом месте, где представлен контроллер, казалось излишним. Более того, в то время у нас были более серьезные вопросы, поэтому в настоящее время или, по крайней мере, до тех пор, пока мы не обновим наши проекты и не исправим все проблемы с пользовательским интерфейсом, мы решили пойти по методу подбора методов.

Рабочее решение представлено в моем ответе, но я был бы признателен за любые отзывы о том, какие могут быть недостатки или последствия такого подхода.

bevoy
источник

Ответы:

12

Вот как мы добились этого с помощью метода Swizzling:


Objective-C

UIViewController + iOS13Fixes.h

#import <Foundation/Foundation.h>

@interface UIViewController (iOS13Fixes)
@end

UIViewController + iOS13Fixes.m

#import <objc/runtime.h>

@implementation UIViewController (iOS13Fixes)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(presentViewController:animated:completion:);
        SEL swizzledSelector = @selector(swizzled_presentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL methodExists = !class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

        if (methodExists) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        } else {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
    });
}

- (void)swizzled_presentViewController:(nonnull UIViewController *)viewController animated:(BOOL)animated completion:(void (^)())completion {

    if (@available(iOS 13.0, *)) {
        if (viewController.modalPresentationStyle == UIModalPresentationAutomatic || viewController.modalPresentationStyle == UIModalPresentationPageSheet) {
            viewController.modalPresentationStyle = UIModalPresentationFullScreen;
        }
    }

    [self swizzled_presentViewController:viewController animated:animated completion:completion];
}

@end

стриж

UIViewController + iOS13Fixes.swift

import UIKit

@objc public extension UIViewController {

    private func swizzled_present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?) {

        if #available(iOS 13.0, *) {
            if viewControllerToPresent.modalPresentationStyle == .automatic || viewControllerToPresent.modalPresentationStyle == .pageSheet {
                viewControllerToPresent.modalPresentationStyle = .fullScreen
            }
        }

        self.swizzled_present(viewControllerToPresent, animated: animated, completion: completion)
    }

    @nonobjc private static let _swizzlePresentationStyle: Void = {
        let instance: UIViewController = UIViewController()
        let aClass: AnyClass! = object_getClass(instance)

        let originalSelector = #selector(UIViewController.present(_:animated:completion:))
        let swizzledSelector = #selector(UIViewController.swizzled_present(_:animated:completion:))

        let originalMethod = class_getInstanceMethod(aClass, originalSelector)
        let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector)

        if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
            if !class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) {
                method_exchangeImplementations(originalMethod, swizzledMethod)
            } else {
                class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            }
        }
    }()

    @objc static func swizzlePresentationStyle() {
        _ = self._swizzlePresentationStyle
    }
}

и, in AppDelegate, application:didFinishLaunchingWithOptions:вызвать swizzling, позвонив (только для быстрой версии):

UIViewController.swizzlePresentationStyle()

Убедитесь, что он вызывается только один раз (используйте dispatch_once или какой-нибудь эквивалент).


Подробнее о методе Swizzling здесь:

bevoy
источник
1
Одной из проблем может быть запуск на iPad, где вам действительно нужен лист страницы, а не полный экран. Возможно, вы захотите обновить чек, чтобы переключить автоматический режим на полный экран и сделать это только в том случае, если представительный контроллер представления имеет черты компактной ширины.
rmaddy
Это решение хорошо? Что если кто-то действительно хочет представить ViewController как .pageSheet?
Ибрагимильмаз
1
@ibrahimyilmaz затем установить viewController.modalPresentationStyleна .pageSheetи вызов self.swizzled_present(:,:,:). Возможно, это не очень красиво, но весь смысл этого поста был основан на предположении, что у вас уже есть существующий проект с большим количеством вызовов для модального представления, и вы хотите восстановить поведение до iOS13 без обновления каждой строки кода.
Бева