Altere modalPresentationStyle no iOS13 em todas as instâncias do UIViewController de uma só vez usando o método swizzling

11

[P&R] É possível alterar o UIViewController.modalPresentationStylevalor globalmente no iOS 13 para que ele se comporte como no iOS 12 (ou anterior)?


Por quê?

No iOS 13 SDK, o valor padrão da UIViewController.modalPresentationStylepropriedade foi alterado UIModalPresentationFullScreenpara o UIModalPresentationAutomaticqual, até onde eu sei, foi resolvido UIModalPresentationPageSheetem dispositivos iOS ou pelo menos em iPhones.

Como o projeto em que estou trabalhando há vários anos se tornou bastante grande, há dezenas de locais onde um controlador de exibição é apresentado. O novo estilo de apresentação nem sempre corresponde aos designs de nossos aplicativos e, às vezes, faz com que a interface do usuário desmorone. Por isso, decidimos mudar UIViewController.modalPresentationStylepara UIModalPresentationFullScreenas versões anteriores ao iOS13 SDK.

Mas adicionar viewController.modalPresentationStyle = UIModalPresentationFullScreenantes de ligar presentViewController:animated:completion:em todos os lugares em que um controlador é apresentado parecia um exagero. Além disso, tínhamos assuntos mais sérios para tratar naquele momento, e é por isso que, por enquanto ou pelo menos até atualizarmos nossos projetos e corrigirmos todos os problemas da interface do usuário, decidimos seguir a abordagem de swizzling de método.

A solução de trabalho é apresentada na minha resposta, mas eu gostaria de receber qualquer feedback que me dissesse quais poderiam ser as desvantagens ou consequências de tal abordagem.

bevoy
fonte

Respostas:

12

Aqui está como conseguimos isso usando o método swizzling:


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

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

e AppDelegate, application:didFinishLaunchingWithOptions:invoque o swizzling chamando (apenas versão rápida):

UIViewController.swizzlePresentationStyle()

Certifique-se de que ele seja chamado apenas uma vez (use dispatch_onceou equivalente).


Mais sobre método swizzling aqui:

bevoy
fonte
11
Um problema seria executado em um iPad em que você realmente deseja uma folha de página e não a tela inteira. Convém atualizar sua verificação para alterar apenas a tela automática para tela cheia e fazê-lo apenas quando o controlador de exibição em apresentação tiver características de largura compactas.
rmaddy
Esta solução é boa? E se alguém realmente quiser apresentar um ViewController como .pageSheet?
Ibrahimyilmaz 18/11/19
11
@ibrahimyilmaz seguida, definir viewController.modalPresentationStylea .pageSheete chamada self.swizzled_present(:,:,:). Talvez não seja muito bonito, mas o ponto principal deste post foi baseado no pressuposto de que você já possui um projeto existente com muitas chamadas para apresentação modal e deseja restaurar o comportamento anterior ao iOS13 sem atualizar cada linha de código.
bevoy