Zmień modalPresentationStyle na iOS13 we wszystkich instancjach UIViewController jednocześnie, używając metody swizzling

11

[Pytania i odpowiedzi] Czy można zmienić UIViewController.modalPresentationStylewartość globalnie w systemie iOS 13, aby zachowywał się tak, jak w systemie iOS 12 (lub wcześniejszym)?


Dlaczego?

W iOS SDK 13 domyślna wartość UIViewController.modalPresentationStylenieruchomości uległa zmianie od UIModalPresentationFullScreencelu UIModalPresentationAutomatic, który jest, o ile wiem, postanowił UIModalPresentationPageSheetna urządzeniach z systemem iOS lub przynajmniej na iPhone.

Ponieważ projekt, nad którym pracuję od kilku lat, stał się dość duży, istnieją dziesiątki miejsc, w których prezentowany jest kontroler widoku. Nowy styl prezentacji nie zawsze pasuje do naszych projektów aplikacji i czasami powoduje rozpad interfejsu użytkownika. Dlatego postanowiliśmy UIViewController.modalPresentationStylewrócić do UIModalPresentationFullScreenwersji SDK wcześniejszych niż iOS13.

Ale dodawanie viewController.modalPresentationStyle = UIModalPresentationFullScreenprzed wywołaniem presentViewController:animated:completion:w każdym miejscu, w którym prezentowany jest kontroler, wydawało się przesadą. Co więcej, mieliśmy wówczas do czynienia z poważniejszymi sprawami, dlatego na razie, a przynajmniej do czasu aktualizacji naszych projektów i naprawienia wszystkich problemów z interfejsem, zdecydowaliśmy się na podejście oparte na zamianie metod.

Rozwiązanie robocze zostało przedstawione w mojej odpowiedzi, ale byłbym wdzięczny za wszelkie informacje zwrotne mówiące mi, jakie mogą być wady lub konsekwencje takiego podejścia.

bevoy
źródło

Odpowiedzi:

12

Oto jak to osiągnęliśmy, stosując metodę swizzling:


Cel 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

Szybki

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
    }
}

i in AppDelegate, application:didFinishLaunchingWithOptions:wywołać swizzling, dzwoniąc (tylko szybka wersja):

UIViewController.swizzlePresentationStyle()

Upewnij się, że jest wywoływany tylko raz (użyj dispatch_oncelub jakiś odpowiednik).


Więcej na temat metody swizzling tutaj:

bevoy
źródło
1
Jednym z problemów może być uruchomienie na iPadzie, w którym tak naprawdę potrzebujesz strony, a nie pełnego ekranu. Możesz zaktualizować czek, aby zmienić tylko automatyczny na pełny ekran i zrobić to tylko wtedy, gdy kontroler widoku prezentującego ma cechy niewielkiej szerokości.
rmaddy
Czy to rozwiązanie jest dobre? Co jeśli ktoś naprawdę chce przedstawić ViewController jako .pageSheet?
ibrahimyilmaz
1
@ibrahimyilmaz następnie ustawić viewController.modalPresentationStylesię .pageSheeti zadzwoń self.swizzled_present(:,:,:). Może to nie jest bardzo ładne, ale cały punkt tego postu opierał się na założeniu, że masz już rozwijający się projekt z dużą liczbą wezwań do prezentacji modalnej i chcesz przywrócić zachowanie sprzed iOS13 bez aktualizacji każdej linii kodu.
bevoy