Ändern Sie modalPresentationStyle unter iOS13 auf allen UIViewController-Instanzen gleichzeitig mithilfe der Methode swizzling

11

[Fragen und Antworten] Ist es möglich, den UIViewController.modalPresentationStyleWert unter iOS 13 global zu ändern, sodass er sich wie früher unter iOS 12 (oder früher) verhält?


Warum?

In iOS 13 SDK von dem Standardwert UIViewController.modalPresentationStyleEigenschaft wird geändert von UIModalPresentationFullScreenzu UIModalPresentationAutomaticwas, soweit ich weiß, gelöst UIModalPresentationPageSheetauf iOS - Geräten oder zumindest auf iPhones.

Da das Projekt, an dem ich seit mehreren Jahren arbeite, ziemlich groß geworden ist, gibt es Dutzende von Stellen, an denen ein View Controller vorgestellt wird. Der neue Präsentationsstil passt nicht immer zu unseren App-Designs und manchmal fällt die Benutzeroberfläche auseinander. Aus diesem Grund haben wir uns entschlossen, UIViewController.modalPresentationStyleauf UIModalPresentationFullScreendie Versionen vor iOS13 SDK zurückzugreifen.

Das Hinzufügen viewController.modalPresentationStyle = UIModalPresentationFullScreenvor dem Anrufen presentViewController:animated:completion:an jedem einzelnen Ort, an dem ein Controller präsentiert wird, schien jedoch ein Overkill zu sein. Darüber hinaus hatten wir zu diesem Zeitpunkt ernstere Probleme zu lösen, weshalb wir uns vorerst oder zumindest bis zur Aktualisierung unserer Designs und zur Behebung aller Probleme mit der Benutzeroberfläche für einen Methoden-Swizzling-Ansatz entschieden haben.

Die funktionierende Lösung wird in meiner Antwort vorgestellt, aber ich würde mich über jedes Feedback freuen, das mir sagt, welche Nachteile oder Konsequenzen ein solcher Ansatz haben könnte.

bevoy
quelle

Antworten:

12

So haben wir das mit der Methode Swizzling erreicht:


Ziel 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

Schnell

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

und in AppDelegate, in application:didFinishLaunchingWithOptions:rufen Sie das Swizzling auf, indem Sie aufrufen (nur schnelle Version):

UIViewController.swizzlePresentationStyle()

Stellen Sie sicher, dass es nur einmal aufgerufen wird (verwenden Sie dispatch_onceoder ein gleichwertiges).


Mehr zum Methoden-Swizzling hier:

bevoy
quelle
1
Ein Problem wäre, auf einem iPad zu laufen, auf dem Sie tatsächlich ein Seitenblatt und keinen Vollbildmodus wünschen. Möglicherweise möchten Sie Ihre Prüfung aktualisieren, um nur automatisch in den Vollbildmodus zu wechseln, und dies nur, wenn der Präsentationsansichts-Controller kompakte Breitenmerkmale aufweist.
rmaddy
Ist diese Lösung gut? Was ist, wenn jemand einen ViewController wirklich als .pageSheet präsentieren möchte?
Ibrahimyilmaz
1
@ibrahimyilmaz dann setzen Sie viewController.modalPresentationStyleauf .pageSheetund rufen an self.swizzled_present(:,:,:). Vielleicht ist es nicht sehr hübsch, aber der springende Punkt dieses Beitrags war die Annahme, dass Sie bereits ein bestehendes Projekt mit vielen Aufforderungen zur modalen Präsentation haben und das Verhalten vor iOS13 wiederherstellen möchten, ohne jede Codezeile zu aktualisieren.
Bevoy