为了回应消费者对使用表情符号和GIF交流的兴趣,越来越多的公司正在将GIF动画纳入他们的电子邮件活动、网站和移动应用程序,以努力提高参与度和销售额。
图形交换格式文件是一个图像集合,按顺序播放,使其看起来像在移动。GIF可以用来分享演示,突出产品功能或变化,说明使用案例,或展示品牌个性。
许多流行的聊天应用程序,如iMessage和WhatsApp,以及社交平台,如Reddit或Twitter,支持发送和接收GIF。那么,iOS应用程序呢?嗯,截至目前,在SwiftUI或UIKit中还没有原生的内置支持来使用GIF。
为iOS构建一个高效的GIF图像加载器需要大量的时间和精力,但幸运的是,一些第三方框架是高性能的,可以显示GIF而没有任何帧延迟。
在这篇文章中,我们将演示如何使用一个流行的GIF库,即Flipboard的FLAnimatedImage,只需几行代码就能将GIF添加到你的iOS应用中。在文章的演示部分,我们将利用GIPHY的GIF,这是一个流行的数据库,提供广泛的动画GIF。
让我们开始吧,学习如何在我们的应用程序中加入一些令人惊叹的GIF!
安装FLAnimatedImage
以下三个依赖管理器中的任何一个都可以用来将FLAnimatedImage添加到一个项目中:
- Cocoapods
- Carthage
- Swift软件包管理器
CocoaPods
要使用CocoaPods将FLAnimatedImage添加到项目中,请在Podfile中添加:
pod 'FLAnimatedImage'
然后,去你终端的项目目录,安装这个Pod,像这样:
pod install
Carthage
要将FLAnimatedImage添加到使用Carthage的项目中,请在Cartfile中添加以下代码:
github "Flipboard/FLAnimatedImage"
然后,为了只更新这个库,去你终端的项目目录,运行以下命令:
carthage update FLAnimatedImage
Swift 软件包管理器
要使用Swift Package Manager将FLAnimatedImage添加到项目中,请打开Xcode,进入菜单栏,并选择File > Add Packages。接下来,在项目的URL字段中粘贴以下链接。
https://github.com/Flipboard/FLAnimatedImage
然后,点击 "Next"并选择项目目标,该库应该被添加到该项目中。点击继续,你就把FLAnimatedImage
添加到项目中了!
在Objective-C中使用FLAnimatedImage
GitHub repo中的FLAnimatedImage的例子是用Objective-C写的:
#import "FLAnimatedImage.h"
FLAnimatedImage *image = [FLAnimatedImage animatedImageWithGIFData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif"]]];
FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init];
imageView.animatedImage = image;
imageView.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
[self.view addSubview:imageView];
我们想在SwiftUI框架中使用FLAnimatedImage,但在这样做之前,我们应该了解这个库的两个主要类:
FLAnimatedImage
FLAnimatedImageView
FLAnimatedImage
类
FLAnimatedImage
是一个帮助以高性能方式提供帧的类。我们用它来设置 FLAnimatedImageView
类的图像属性。
为了加载一个GIF图像,我们将GIF转换成一个Data
值类型。FLAnimatedImage
有一个初始化器,接受这个Data
。
convenience init(animatedGIFData data: Data!)
在创建一个类型为FLAnimatedImage
的图像实例后,我们可以用它来设置FLAnimatedImageView
的图像属性。
imageView.animatedImage
FLAnimatedImageView
类
FLAnimatedImageView
是 UIImageView
的一个子类,并接受一个FLAnimatedImage
。
class FLAnimatedImageView
FLAnimatedImageView
在视图层次中自动播放GIF,当它被移除时就会停止。我们可以通过访问 animatedImage
属性来设置动画图像。
在Swift中使用FLAnimatedImage
现在我们已经熟悉了FLAnimatedImage类和它们的用法,我们可以像这样把上面的Objective-C例子翻译成Swift。
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif")!
let data = try! Data(contentsOf: url)
/// Creating an animated image from the given animated GIF data
let animatedImage = FLAnimatedImage(animatedGIFData: data)
/// Creating the image view
let imageView = FLAnimatedImageView()
/// Setting the animated image to the image view
imageView.animatedImage = animatedImage
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view.addSubview(imageView)
这个例子与UIKit有关。在UIKit中使用FLAnimatedImage非常容易,因为它是原生的。然而,为了在SwiftUI中使用FLAnimatedImage,我们必须创建一个自定义视图,利用UIViewRepresentable
,这是一个UIKit视图的包装器,我们用它来整合到SwiftUI的视图层次中。
因此,让我们创建我们的自定义视图,以简化与FLAnimatedImage的工作
GIF视图
我们将在 SwiftUI 中创建一个名为GIFView
的自定义视图,它可以帮助我们从本地资产和远程 URL 中加载 GIF。
在创建自定义视图之前,让我们创建一个枚举URLType
,定义两种情况:
name
:本地文件的名称url
: 必须从服务器获取的GIF的URL
enum URLType {
case name(String)
case url(URL)
var url: URL? {
switch self {
case .name(let name):
return Bundle.main.url(forResource: name, withExtension: "gif")
case .url(let remoteURL):
return remoteURL
}
}
}
在上面的代码中,我们可以看到还有一个计算属性url
,如果我们同时提供远程URL和GIF的名称,就会返回本地URL。
我们创建一个新的文件,GIFView.swift
。在这个文件中,我们将导入FLAnimatedImage
库。
import FLAnimatedImage
接下来,我们创建一个struct
,GIFView,它符合UIViewRepresentable
协议。这个结构有一个初始化器,接收URL类型。
struct GIFView: UIViewRepresentable {
private var type: URLType
init(type: URLType) {
self.type = type
}
}
然后,我们添加两个闭包,即FLAnimatedImageView
和UIActivityIndicatorView
的实例,以便在GIF加载时显示一个活动指示器。
private let imageView: FLAnimatedImageView = {
let imageView = FLAnimatedImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.cornerRadius = 24
imageView.layer.masksToBounds = true
return imageView
}()
private let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
return activityIndicator
}()
在上面的代码中,我们为FLAnimatedImageView
的转角半径指定了一个值24
,但我们可以根据自己的意愿来定制。
UIViewRepresentable
协议有两个必须实现的方法。第一个,makeUIView(context:)
,创建视图对象,UIView
,并配置初始状态。第二个,updateUIView(_:context:)
,更新指定视图的状态。
在makeUIView(context:)
方法中,我们:
- 创建一个
UIView
- 将两个视图,
makeUIView(context:)
和updateUIView(_:context:)
,作为子视图添加到UIView
中。 - 激活必要的约束
- 返回视图
UIView
下面是makeUIView(context:)
方法的代码:
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)
view.addSubview(activityIndicator)
view.addSubview(imageView)
imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
return view
}
在updateUIView(_:context:)
方法中,我们:
- 开始制作活动指示器的动画
- 检查URL是否是可选的;如果是可选的,我们从该方法中返回
- 创建一个
Data
的实例,其中包含URL的内容 - 创建一个
FLAnimatedImage
的实例,传递GIF动画的数据 - 停止活动指示器的动画,并将
FLAnimatedView
's animated image属性设置为所获取的图像。
下面是updateUIView(_:context:)
方法的代码:
func updateUIView(_ uiView: UIView, context: Context) {
activityIndicator.startAnimating()
guard let url = type.url else { return }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
let image = FLAnimatedImage(animatedGIFData: data)
DispatchQueue.main.async {
activityIndicator.stopAnimating()
imageView.animatedImage = image
}
}
}
}
加载GIF动画日期需要时间,所以我们在后台的并发线程上运行它以避免阻塞UI。然后,我们在主线程上设置图片。
下面是GIFView
的最终代码:
import SwiftUI
import FLAnimatedImage
struct GIFView: UIViewRepresentable {
private var type: URLType
init(type: URLType) {
self.type = type
}
private let imageView: FLAnimatedImageView = {
let imageView = FLAnimatedImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.cornerRadius = 24
imageView.layer.masksToBounds = true
return imageView
}()
private let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
return activityIndicator
}()
}
extension GIFView {
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)
view.addSubview(activityIndicator)
view.addSubview(imageView)
imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
activityIndicator.startAnimating()
guard let url = type.url else { return }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
let image = FLAnimatedImage(animatedGIFData: data)
DispatchQueue.main.async {
activityIndicator.stopAnimating()
imageView.animatedImage = image
}
}
}
}
}
现在我们有了一个将FLAnimatedImage
与SwiftUI一起使用的视图,在SwiftUI中使用它就像在UIKit中一样简单。
让我们来探讨两个简单的例子,以及一个更复杂的、真实世界的例子。
简单的 GIF 演示。FLAnimatedImage和SwiftUI
我们将从assets文件夹中存在的一个简单的GIF例子开始。首先,下载我们将在本例中使用的 GIF,happy-work-from-home
。
我们使用之前描述的方法创建GIFView
,。接下来,我们所要做的就是传入GIF的名称,像这样:
struct ContentView: View {
var body: some View {
GIFView(type: .name("happy-work-from-home"))
.frame(maxHeight: 300)
.padding()
}
}
在模拟器上运行这段代码,我们就可以得到一个GIF动画:
下面是另一个例子,使用同样的GIF,但从一个远程URL获取。
struct ContentView: View {
var body: some View {
GIFView(type: .url(URL(string: "https://media.giphy.com/media/Dh5q0sShxgp13DwrvG/giphy.gif")!))
.frame(maxHeight: 300)
.padding()
}
}
如果我们在模拟器上运行这段代码,我们会看到一个GIF动画。注意,在获取GIF的同时,屏幕上会显示一个活动指示器:
现在,让我们来看看一个更复杂的、真实世界的例子!
复杂的、真实世界的GIF演示
为了充分利用FLAnimatedImage的优势,让我们来探讨一个同时加载许多GIF的例子。这个例子将展示这个框架在内存压力下的性能,通过显示和流畅地滚动大量的GIF图片。
GIPHY是最大的GIFs市场。它也有一个开发者项目,允许我们使用所提供的API来获取他们的数据。我们将使用它的一个API来获取流行的GIF,并将它们显示在一个列表中。
首先,在这里创建一个GIPHY账户。登录后,点击创建新的应用程序并选择API。然后,输入你的应用程序名称和描述,并确认协议。接下来,点击创建应用程序按钮,并注意到API密钥。
趋势端点返回当天最相关和最吸引人的内容列表。它看起来像这样:
http://api.giphy.com/v1/gifs/trending
为了验证,我们将API密钥作为参数传递。我们还可以通过使用limit
参数来限制一个响应中的GIF数量,offset
,以获得下一组响应。
该端点返回一个巨大的JSON文件,但我们要修剪它以满足我们的要求。为此,创建另一个文件,GIFs.swift
,并添加以下代码:
import Foundation
struct GIFs: Codable {
let data: [GIF]
}
struct GIF: Codable, Identifiable {
let id: String
let title: String
let images: Images
}
struct Images: Codable {
let original: GIFURL
}
struct GIFURL: Codable {
let url: String
}
端点返回一个GIF
的数组,每个GIF
都有一个标题和一个图像URL。
继续创建用户界面,我们添加一个变量来存储GIF,并跟踪offset
。我们的想法是,当我们用拉到刷新的手势刷新屏幕时,它就会获取下一批数据:
struct ContentView: View {
@State private var gifs: [GIF] = []
@State private var offset = 0
}
接下来,我们将添加一个方法,从趋势端点获取GIF数据:
extension ContentView {
private func fetchGIFs() async {
do {
try await fetchGIFData(offset: offset)
} catch {
print(error)
}
}
private func fetchGIFData(for limit: Int = 10, offset: Int) async throws {
var components = URLComponents()
components.scheme = "https"
components.host = "api.giphy.com"
components.path = "/v1/gifs/trending"
components.queryItems = [
.init(name: "api_key", value: "<API KEY>"), // <-- ADD THE API KEY HERE
.init(name: "limit", value: "\(limit)"),
.init(name: "offset", value: "\(offset)")
]
guard let url = components.url else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
gifs = try JSONDecoder().decode(GIFs.self, from: data).data
}
}
上面的代码执行了以下动作:
- 创建趋势端点的URL,并添加查询参数
api_key
,limit
, andoffset
- 从URL中获取数据并使用
GIFs
结构对其进行解码 - 设置数据到变量中
gifs
对于用户界面,我们有一个列表,在gifs
的数组上加载,并在获取URL的GIFView
中显示单个GIF。
extension ContentView {
var body: some View {
NavigationView {
if gifs.isEmpty {
VStack(spacing: 10) {
ProgressView()
Text("Loading your favorite GIFs...")
}
} else {
List(gifs) { gif in
if let url = URL(string: gif.images.original.url) {
GIFView(type: .url(url))
.frame(minHeight: 200)
.listRowSeparator(.hidden)
}
}
.listStyle(.plain)
.navigationTitle("GIPHY")
}
}
.navigationViewStyle(.stack)
.task {
await fetchGIFs()
}
.refreshable {
offset += 10
await fetchGIFs()
}
}
}
当通过下拉屏幕刷新视图时,它通过10
增加offset
,获取新的GIF,并更新屏幕。
下面是一个屏幕记录,显示了视图被刷新时发生的情况:
就这样,我们创建了一个完整的高性能应用程序,显示了来自GIPHY的趋势GIF!
结论
我们必须投入大量的精力来创建一个处理GIF的高性能动画UIImageView
。另外,我们可以利用现有的FLAnimatedImage框架,它被许多流行的应用程序所使用,如Facebook、Instagram、Slack和Telegram。
有了自定义的GIFView
,我们只需要在SwiftUI中写一行代码,就可以在我们的应用程序中显示令人惊叹的GIF了祝你实验和编码愉快!GIF动画只是增强你的iOS应用的用户体验和用户界面的众多方法之一。