Adaptando sua solução comercial existente para o SwiftUI. Parte 2

Bom Dia a todos! Com você, Anna Zharkova, desenvolvedora móvel líder da Usetech.Nesta
parte, já discutiremos o caso de como adaptar uma solução pronta para uso em um projeto no SwiftUI. Se você não estiver familiarizado com essa tecnologia, recomendamos que você se familiarize com uma breve introdução ao tópico .

Portanto, vejamos um exemplo simples de como você pode usar uma biblioteca pronta para um aplicativo iOS padrão em um aplicativo no SwiftUI.

Vamos dar a solução clássica: fazer upload de imagens de forma assíncrona usando a biblioteca SDWebImage.



Por conveniência, o trabalho com a biblioteca é encapsulado no ImageManager, que chama:

  • SDWebImageDownloader
  • SDImageCache

para baixar imagens e armazenar em cache.

Por tradição, a comunicação com o resultado do UIImageView de recebimento é implementada de duas maneiras:

  • passando links fracos para o mesmo UIImageView;
  • passando o bloco de fechamento para o método ImageManager


Uma chamada para o ImageManager geralmente é encapsulada na extensão UIImageView:

extension UIImageView {
 func setup(by key: String) {
        ImageManager.sharedInstance.setImage(toImageView: self, forKey: key)
    }
}

na classe sucessora:

class CachedImageView : UIImageView {
    private var _imageUrl: String?
    var imageUrl: String?  {
        get {
            return _imageUrl
        }
        set {
            self._imageUrl = newValue
            if let url = newValue, !url.isEmpty {
                self.setup(by: url)
            }
        }
    }
    
    func setup(by key: String) {
        ImageManager.sharedInstance.setImage(toImageView: self, forKey: key)
    }
}

Agora vamos tentar estragar esta solução no SwiftUI. No entanto, ao adaptar, devemos levar em consideração os seguintes recursos da estrutura:

- Exibir - estrutura. Herança não é suportada


- A extensão no sentido usual é inútil. Obviamente , podemos escrever alguns métodos para estender a funcionalidade, mas precisamos vincular isso ao DataFlow ;

Temos o problema de obter feedback e a necessidade de adaptar toda a lógica da interação com a interface do usuário ao DataDriven Flow.

Para a solução, podemos ir do lado da visualização e do lado da adaptação do fluxo de dados.

Vamos começar com o modo de exibição.

Para começar, lembre-se de que o SwiftUI não existe por si só, mas como um complemento para o UIKit. Os desenvolvedores do SwiftUI forneceram um mecanismo para uso no SwiftUI UIView, cujos análogos não estão entre os controles prontos. Para esses casos, existem os protocolos UIViewRepresentable e UIViewControllerRepresentable para adaptar o UIView e o UIViewController, respectivamente.

Crie uma estrutura View que implemente um UIViewRepresentable, na qual redefinimos os métodos:

  • makeUiView;
  • updateUIView

em que indicamos qual UIView estamos usando e definimos suas configurações básicas. E não se esqueça de PropertyWrappers para propriedades mutáveis.

struct WrappedCachedImage : UIViewRepresentable {
    let height: CGFloat
    @State var imageUrl: String
    
    func makeUIView(context: Context) -> CachedImageView {
        let frame = CGRect(x: 20, y: 0, width: UIScreen.main.bounds.size.width - 40, 
                                     height: height)
        return CachedImageView(frame: frame)
    }
    
    func updateUIView(_ uiView: CachedImageView, context: Context) {
        uiView.imageUrl = imageUrl
        uiView.contentMode = .scaleToFill
    }
}

Podemos incorporar o novo controle resultante no View SwiftUI:



Essa abordagem tem vantagens:

  • Não há necessidade de alterar a operação de uma biblioteca existente
  • A lógica é encapsulada no UIView incorporado.

Mas há novas responsabilidades. Primeiro, você precisa monitorar o gerenciamento de memória no pacote View-UIView. Desde a estrutura View, todo o trabalho com eles é realizado em segundo plano pela própria estrutura. Mas a limpeza de novos objetos cai sobre os ombros do desenvolvedor.

Em segundo lugar, são necessárias etapas adicionais para personalização (tamanhos, estilos). Se em Exibir essas opções estiverem ativadas por padrão, elas deverão ser sincronizadas com o UIView.

Por exemplo, para ajustar os tamanhos, podemos usar o GeometryReader para que nossa imagem ocupe toda a largura da tela e a altura definida por nós:

 var body: some View {
      GeometryReader { geometry in 
        VStack {
           WrappedCachedImage(height:300, imageUrl: imageUrl) 
           .frame(minWidth: 0, maxWidth: geometry.size.width,
                     minHeight: 0, maxHeight: 300)
        }
    }
}

Em princípio, nesses casos, o uso do UIView incorporado pode ser considerado como superengenharia . Então agora vamos tentar resolver através do DataFlow SwiftUI.

A visão que temos depende de uma variável de estado ou de um grupo de variáveis, ou seja, de um determinado modelo, que por si só pode ser essa variável de estado. Em essência, essa interação é baseada no padrão MVVM.

Implementamos da seguinte maneira:

  • crie uma View personalizada, dentro da qual usaremos o controle SwiftUI;
  • crie um ViewModel no qual transferimos a lógica de trabalhar com o Model (ImageManager).


Para que haja uma conexão entre o View e o ViewModel, o ViewModel deve implementar o protocolo ObservableObject e conectar-se ao View como um ObservedObject .

class CachedImageModel : ObservableObject {
    @Published var image: UIImage = UIImage()
    
    private var urlString: String = ""
    
    init(urlString:String) {
        self.urlString = urlString
    }
    
    func loadImage() {
        ImageManager.sharedInstance
        .receiveImage(forKey: urlString) {[weak self] (im) in
            guard let self = self else {return}
            DispatchQueue.main.async {
                self.image = im
            }
        }
    }
}

A exibição no método onAppear do seu ciclo de vida chama o método ViewModel e obtém a imagem final de sua propriedade @Published:

struct CachedLoaderImage : View {
    @ObservedObject var  model:CachedImageModel
    
    init(withURL url:String) {
        self.model = CachedImageModel(urlString: url)
    }
    
    var body: some View {
        Image(uiImage: model.image)
            .resizable()
            .onAppear{
                self.model.loadImage()
        }
    }
    
}

Há também uma API combinada declarativa para trabalhar com o DataFlow SwiftUI . Trabalhar com ele é muito parecido com trabalhar com estruturas reativas (o mesmo RxSwift): existem assuntos, existem assinantes, existem métodos de gerenciamento semelhantes, há cancelamentos (em vez de descartáveis).

class ImageLoader: ObservableObject {
 @Published var image: UIImage?
 private var cancellable: AnyCancellable?

 func load(url: String) {
     cancellable = ImageManager.sharedInstance.publisher(for: url)
         .map { UIImage(data: $0.data) }
         .replaceError(with: nil)
         .receive(on: DispatchQueue.main)
         .assign(to: \.image, on: self)
 }

Se nosso ImageManager foi originalmente escrito usando Combine, a solução seria assim.

Mas desde O ImageManager é implementado com outros princípios, e tentaremos de outra maneira. Para gerar um evento, usaremos o mecanismo PasstroughSubject, que suporta o preenchimento automático de assinaturas.

    var didChange = PassthroughSubject<UIImage, Never>()

Enviaremos um novo valor ao atribuir um valor à propriedade UIImage do nosso modelo:

var data = UIImage() {
        didSet {
            didChange.send(data)
        }
    }
 ,    .

O valor final da nossa exibição "escuta" no método onReceive:

 var body: some View {
       Image(uiImage: image)
            .onReceive(imageLoader.didChange) { im in
            self.image = im
           //-   
        }
    }

Então, vimos um exemplo simples de como você pode adaptar o código existente ao SwiftUI.
O que resta a acrescentar. Se a solução iOS existente afetar mais a parte da interface do usuário, é melhor usar a adaptação por meio do UIViewRepresentable. Em outros casos, é necessária a adaptação do modelo View do estado.

Nas partes a seguir, veremos como adaptar a lógica de negócios de um projeto existente ao SwiftUI , trabalhar com navegação e , em seguida, aprofundar a adaptação ao Combine.

Para obter mais informações sobre como trabalhar com o View no SwiftUI, consulte aqui .

All Articles