Cambie modalPresentationStyle en iOS13 en todas las instancias de UIViewController a la vez utilizando el método swizzling

11

[Preguntas y respuestas] ¿Es posible cambiar el UIViewController.modalPresentationStylevalor globalmente en iOS 13 para que se comporte como solía hacerlo en iOS 12 (o anterior)?


¿Por qué?

En el SDK de iOS 13, el valor predeterminado de la UIViewController.modalPresentationStylepropiedad se ha cambiado UIModalPresentationFullScreeny UIModalPresentationAutomatic, por lo que sé, se resuelve UIModalPresentationPageSheeten dispositivos iOS o, al menos, en iPhone.

Dado que el proyecto en el que he estado trabajando durante varios años se ha vuelto bastante grande, hay decenas de lugares donde se presenta un controlador de vista. El nuevo estilo de presentación no siempre coincide con los diseños de nuestras aplicaciones y, a veces, hace que la interfaz de usuario se desmorone. Por eso, decidimos cambiar de UIViewController.modalPresentationStylenuevo a UIModalPresentationFullScreenlas versiones de SDK anteriores a iOS13.

Pero agregar viewController.modalPresentationStyle = UIModalPresentationFullScreenantes de llamar presentViewController:animated:completion:en cada lugar donde se presenta un controlador parecía una exageración. Además, teníamos asuntos más serios que tratar en ese momento, razón por la cual, por el momento o al menos hasta que actualicemos nuestros diseños y arreglemos todos los problemas de la interfaz de usuario, decidimos optar por un enfoque de método vertiginoso.

La solución de trabajo se presenta en mi respuesta, pero agradecería cualquier comentario que me diga cuáles serían las desventajas o las consecuencias de tal enfoque.

bevoy
fuente

Respuestas:

12

Así es como lo logramos usando el método swizzling:


C objetivo

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

Rápido

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

y AppDelegate, para application:didFinishLaunchingWithOptions:invocar el swizzling llamando (solo la versión swift):

UIViewController.swizzlePresentationStyle()

Asegúrese de que se llame solo una vez (use dispatch_onceo algún equivalente).


Más sobre el método swizzling aquí:

bevoy
fuente
1
Un problema sería ejecutarlo en un iPad donde realmente desea una hoja de página y no una pantalla completa. Es posible que desee actualizar su cheque para cambiar solo automático a pantalla completa y solo hacerlo cuando el controlador de vista de presentación tenga rasgos de ancho compacto.
rmaddy
¿Es buena esta solución? ¿Qué pasa si alguien realmente quiere presentar un ViewController como .pageSheet?
ibrahimyilmaz
1
@ibrahimyilmaz continuación, establece viewController.modalPresentationStylea .pageSheety llamada self.swizzled_present(:,:,:). Tal vez no sea muy bonito, pero el objetivo de esta publicación se basó en el supuesto de que ya tiene un proyecto extenso con muchas llamadas para presentación modal y que desea restaurar el comportamiento anterior a iOS13 sin actualizar cada línea de código.
Bevoy