SwiftUI 播放GIF的实现方案

2022-07-19  本文已影响0人  健了个平_24

突然发现SwiftUI的Image貌似不支持播放GIF,那就只能自己尝试实现一把。

Demo:Github地址

实现方案

1. SwiftUI中使用UIKit - UIViewRepresentable

SwiftUIImageAsyncImage目前发现并不支持播放GIF,既然如此,最简单的实现就是将UIKitUIImageView应用到SwiftUI中。

SwiftUI中使用UIKit控件,就得用到UIViewRepresentable协议去实现了:

import SwiftUI
import UIKit

struct GifImage: UIViewRepresentable {
    // GIF模型
    var resource: GifResource?
    // UIKit的内容显示模式
    var contentMode: UIView.ContentMode = .scaleAspectFill
    // 用于控制GIF的播放/暂停
    @Binding var isAnimating: Bool
    
    func makeUIView(context: Context) -> MyView { MyView() }
    
    func updateUIView(_ uiView: MyView, context: Context) {
        uiView.contentMode = contentMode
        uiView.updateGifResource(resource, isAnimating)
    }
    
    ......
}

其中MyView是我自己自定义的一个UIView,上面放着一个UIImageView专门播放GIF:

class MyView: UIView {
    private let imageView = UIImageView()
    private var resource: GifResource?
        
    init() {
        super.init(frame: .zero)
        clipsToBounds = true
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imageView)
        NSLayoutConstraint.activate([
            imageView.widthAnchor.constraint(equalTo: widthAnchor),
            imageView.heightAnchor.constraint(equalTo: heightAnchor),
        ])
    }
        
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
        
    override var contentMode: UIView.ContentMode {
        set { imageView.contentMode = newValue }
        get { imageView.contentMode }
    }
        
    ......
}

2. 解码GIF文件

GIF的解码过程我写在了UIImage的分类中,并且使用了async/await适配SwiftUI,方便调起:

import UIKit

extension UIImage {
    static func decodeGif(fromBundle name: String) async throws -> GifResource {
        ......
    }
    
    static func decodeGif(withUrl url: URL?) async throws -> GifResource {
        ......
    }
    
    static func decodeGif(withData data: Data) async throws -> GifResource {
        ......
    }
}

3. 用起来

struct ContentView: View {
    @State var resource: GifResource? = nil
    
    var body: some View {
        VStack {
            GifImage(resource: resource, 
                     contentMode: .scaleAspectFit, 
                     isAnimating: .constant(true))
                .frame(width: 150, height: 150)
                .background(.ultraThinMaterial)
                .mask(RoundedRectangle(cornerRadius: 10, style: .continuous))
                .shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 10)
        }
        .task {
            resource = try? await UIImage.decodeGif(fromBundle: "Cat2")
        }
    }
}

4. AsyncGifImage - 异步加载远程/本地GIF

基于GifImage的扩展,一个可异步加载远程/本地GIF的View

/// 仿照`AsyncImage`
AsyncGifImage(url: url,
              contentMode: .scaleAspectFit,
              transaction: Transaction(animation: .easeInOut),
              isAnimating: $isAnimating,
              isReLoad: $isReload) { phase in
    switch phase {
        // 请求中
        case .loading: ProgressView()
        // 请求成功
        case let .success(image): image // image为GifImage
        // 请求失败
        case .failure: Text("Failure").font(.body.weight(.bold))
    }
}

最终效果

effect.gif

OK, done.

Demo:Github地址

上一篇下一篇

猜你喜欢

热点阅读