如何在 SwiftUI 中对 CoreImage 滤镜做实时预览


网罗开发 (小红书、快手、视频号同名)

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验 。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员

👋 大家好,我是展菲!

📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。

📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。

💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。

📅 最新动态:2025 年 3 月 17 日

快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!

文章目录

前言

在做图片相关功能时,有一个需求几乎绕不开:
用户拖动参数,图片实时变化。

比如:

  • 调整模糊强度
  • 改变对比度、饱和度
  • 预览滤镜效果,再决定是否应用

在 UIKit 时代,我们可能会用 UIImageView + CoreImage + GCD 硬撸。

但到了 SwiftUI,很多人第一反应是:

SwiftUI + CoreImage + 实时预览,这事靠谱吗?

答案是:靠谱,但得用对方式。

这篇文章就从一个最小可用 Demo开始,一步一步把实时滤镜预览这件事讲清楚。

先说结论:实时预览的关键点是什么?

在 SwiftUI 里做 CoreImage 实时预览,核心其实只有三点:

  1. 图片渲染要尽量轻
  2. 滤镜计算不能阻塞主线程
  3. UI 状态变化要最小化

如果你一上来就把所有滤镜计算都丢进 body

那基本等于在和 SwiftUI 的刷新机制正面硬刚。

一个最基础的目标效果

我们先定一个目标:

  • 显示一张原图
  • 拖动 Slider
  • 实时调整高斯模糊强度
  • 图片随着 Slider 连续变化

这是绝大多数滤镜编辑页的基础形态。

Step 1:准备 CoreImage 的基础组件

先把 CoreImage 的几个核心对象准备好:

swift 复制代码
import SwiftUI
import CoreImage
import CoreImage.CIFilterBuiltins

let context = CIContext()
let filter = CIFilter.gaussianBlur()

这里有两个细节值得注意:

  • CIContext 应该尽量复用
  • 不要在 body 里反复 new CIContext

CIContext 本身是重量级对象,频繁创建会直接拖垮性能。

Step 2:一个最简单的 SwiftUI 结构

我们先搭一个最基础的页面结构:

swift 复制代码
struct ContentView: View {
    @State private var intensity: Double = 0.5
    let image = UIImage(named: "example")!

    var body: some View {
        VStack {
            Image(uiImage: image)
                .resizable()
                .scaledToFit()

            Slider(value: $intensity)
                .padding()
        }
    }
}

到这一步,UI 是没问题的,但还没有任何滤镜逻辑

Step 3:把 CoreImage 滤镜接进来

关键思路是:
不要直接操作 UIImage,而是用 CIImage 作为中间态。

我们先写一个专门负责"生成滤镜图片"的方法:

swift 复制代码
func applyProcessing() -> UIImage {
    let beginImage = CIImage(image: image)
    filter.inputImage = beginImage
    filter.radius = Float(intensity * 20)

    guard let outputImage = filter.outputImage else {
        return image
    }

    if let cgimg = context.createCGImage(outputImage, from: beginImage!.extent) {
        return UIImage(cgImage: cgimg)
    }

    return image
}

这段代码做了几件事:

  1. UIImage 转成 CIImage
  2. 设置滤镜参数
  3. 通过 CIContext 渲染成 CGImage
  4. 再转回 UIImage

Step 4:把实时预览"接"到 SwiftUI 状态上

接下来是最关键的一步:
让 SwiftUI 在 Slider 变化时刷新图片,但不炸性能。

先引入一个新的状态:

swift 复制代码
@State private var processedImage: UIImage?

然后改造 body

swift 复制代码
var body: some View {
    VStack {
        Image(uiImage: processedImage ?? image)
            .resizable()
            .scaledToFit()

        Slider(value: $intensity)
            .padding()
            .onChange(of: intensity) { _ in
                processedImage = applyProcessing()
            }
    }
}

此时你已经可以看到:

  • Slider 一动
  • 图片跟着变
  • 滤镜是实时的

但------
这还不是一个"能上线"的写法。

性能问题从哪开始暴露?

当你快速拖动 Slider 时,会发现:

  • UI 有轻微卡顿
  • 真机上比模拟器更明显
  • 图片越大,问题越严重

原因也很直接:

滤镜计算跑在主线程。

Slider 的 onChange 本身就在主线程,

CoreImage 渲染又是 CPU / GPU 混合操作,

自然会影响 UI 响应。

Step 5:把滤镜计算移出主线程

一个简单、有效的方式是:
Task + MainActor 控制线程切换。

改造 onChange

swift 复制代码
.onChange(of: intensity) { _ in
    Task.detached {
        let output = applyProcessing()
        await MainActor.run {
            processedImage = output
        }
    }
}

这样做之后:

  • 滤镜计算在后台执行
  • UI 只负责展示结果
  • 拖动 Slider 明显顺滑很多

这一步,是"能不能实时预览"的分水岭。

再往前一步:为什么 SwiftUI 特别适合做这件事?

如果你用 UIKit 做过类似功能,会发现:

  • 手动管理线程
  • 手动刷新 ImageView
  • 状态和 UI 同步很痛苦

而 SwiftUI 的优势在于:

  • 状态驱动 UI
  • 图片只是状态的一个映射
  • 滤镜逻辑和 UI 逻辑可以完全解耦

你只需要保证一件事:

状态更新是轻的,计算是异步的。

一点真实项目里的经验总结

在真实项目中,我一般会遵守这几个原则:

  1. Slider 变化频繁时,必要时做节流
  2. 滤镜链尽量复用,不要每次 new
  3. 大图先 downscale 再做预览
  4. 最终导出时再跑一次"高质量渲染"

实时预览追求的是**"看起来对"
而不是
"每一帧都是最终质量"**。

总结

SwiftUI 并不是不适合做图像处理,

而是不能用同步思维去写异步计算

一旦你把:

  • CoreImage 的计算
  • SwiftUI 的状态刷新
  • 主线程和后台线程的职责

这三件事理顺了,

实时滤镜预览这件事,其实比 UIKit 时代要轻松得多。

相关推荐
光影少年6 分钟前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技12 分钟前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally1 小时前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim148013 小时前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally15 小时前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手18 小时前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero18 小时前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb
ii_best18 小时前
lua语言开发脚本基础、mql命令库开发、安卓/ios基础开发教程,按键精灵新手工具
android·ios·自动化·编辑器
用户223586218202 天前
WebKit WebPage API 的引入尝试与自研实现
ios
啦啦啦!2 天前
ChatGPT和Gemini的接入和封装
人工智能·ios·chatgpt