SwiftUI学习100天(Day65 - 项目 13,第四部分)

原创链接:https://www.hackingwithswift.com/100/swiftui
以下内容仅供学习参考:

今天,我们将练习你刚学到的一些技术,包括如何创建自定义绑定、如何在 SwiftUI 中进行包装以供使用PHPickerViewController
,以及如何将 Core Filter 效果应用于图像。
我希望你注意到的一件事是我如何经常引导你回到我们之前研究过的主题。这不是偶然的:正如自助作家拿破仑希尔所写的那样,“任何想法、计划或目的都可以通过思想的重复出现在脑海中”——这种重复是我用来帮助你内化的几种方法之一所有这些代码如何组合在一起。
即使你今天没有完全理解这些概念,也没关系——我们将来会再次介绍它们。
今天你要完成三个主题,在这些主题中你将练习包装一个UIViewController
以用于 SwiftUI,使用 Core Image 过滤图像,等等。

构建我们的基本 UI
我们项目的第一步是构建基本的用户界面,这个应用程序将是:
一个NavigationView,
这样我们就可以在顶部显示我们应用程序的名称。一个大的灰色框,上面写着“点击以选择图片”,我们将在其上放置他们导入的图片。
“强度”滑块将影响我们应用 Core Image 过滤器的强度,存储为 0.0 到 1.0 之间的值。
“保存”按钮将修改后的图像写入用户的照片库。
最初用户不会选择图像,因此我们将使用@State
可选的图像属性来表示它。
首先将这两个属性添加到ContentView
:
现在将其body
属性的内容修改为:
我喜欢能够在 SwiftUI 布局中放置可选视图,而且我认为它特别适合ZStack
,
因为当用户加载图片时,图片下方的文本会自动被遮挡。
现在,由于我们的代码在这里相当简单,我想简单地探讨一下清理我们的body
财产的样子——我们有很多布局代码,但正如你所看到的,我们还有几个按钮关闭也在那里。
对于非常小的代码片段,我通常很乐意将按钮操作指定为闭包,但是当我们完成后,保存按钮将有很多行,所以我建议将它分解成它自己的函数.
现在这只是意味着向 ContentView
中添加一个空
,如下所示:save()
方法
然后我们会像这样从按钮调用它:
当你学习时,直接在你的视图中编写按钮操作和类似操作是很常见的,但是一旦你开始真正的项目,花额外的时间来清理你的代码是个好主意——从长远来看,这会让你的生活更轻松,相信我!
我将在以后添加更多像这样的小清理技巧,因此当你开始接近课程的结尾时,你会越来越相信你的代码状态良好。



使用 PHPickerViewController 将图像导入 SwiftUI
为了使这个项目栩栩如生,我们需要让用户从他们的图库中选择一张照片,然后将其显示在ContentView
. 我已经向你展示了这一切是如何工作的,所以如果你按照介绍性章节进行操作,你将已经拥有所需的大部分代码。
如果你错过了这些章节,现在还为时不晚:创建一个名为 ImagePicker.swift 的新 Swift 文件,然后将其代码替换为:
这是我们之前看过的所有代码,所以我不打算重新解释它们的作用。相反,我想回到 ContentView.swift 以便我们可以使用它。
首先,我们需要一个@State
布尔值来跟踪我们的图像选择器是否正在显示,因此首先将其添加到ContentView
:
其次,我们需要在点击灰色大矩形时将该布尔值设置为 true,因此将// select an image
注释替换为:
第三,我们需要一个属性来存储用户选择的图像。我们为该ImagePicker
结构提供了一个@Binding
附加到UIImage
的属性
,这意味着当我们创建图像选择器时,我们需要传入 UIImage
以便它链接到。当@Binding
属性改变时,外部值也会改变,这让我们可以读取值。
因此,将此属性添加到ContentView
:
第四,我们需要一个在ImagePicker
视图被关闭时调用的方法。现在这只是将选定的图像直接放入 UI,所以请将此方法添加到ContentView
现在:
然后,只要我们的inputImage
值发生变化,我们就可以调用它,通过在ContentView
某处附加一个
——它在哪里真的不重要,但在onChange()
修饰符navigationTitle()
之后
似乎是明智的。
最后,我们需要在ContentView
某处添加一个修饰符在
。这将sheet()
showingImagePicker
用作它的条件,并呈现一个ImagePicker
绑定到inputImage
作为它的内容。
因此,将其直接添加到现有navigationTitle()
修饰符下方:
这就完成了包装 UIKit 视图控制器以便在 SwiftUI 中使用所需的所有步骤。这次我们过得更快了一点,但希望它仍然有意义!
继续并再次运行该应用程序,你应该能够点击灰色矩形来导入图片,当你找到一张图片时,它将出现在我们的 UI 中。
提示:我们刚刚制作的ImagePicker
视图是完全可重用的——你可以将 Swift 文件放在一边,然后轻松地在其他项目中使用它。如果你考虑一下,包装视图的所有复杂性都包含在 ImagePicker.swift 中,这意味着如果你选择在其他地方使用它,只需显示一个工作表并绑定一个图像即可。



使用 Core Image 进行基本图像过滤
现在我们的项目有用户选择的图像,下一步是让用户对其应用不同的 Core Image 滤镜。首先,我们将只使用一个过滤器,但很快我们将使用确认对话框对其进行扩展。
如果我们想在我们的应用程序中使用 Core Image,我们首先需要在 ContentView.swift 的顶部添加两个导入:
接下来我们需要上下文和过滤器。Core Image 上下文是一个负责将一个CIImage
渲染
到 一个CGImage
的对象,或者更实际地说,是一个将图像的配方转换为我们可以使用的实际像素系列的对象。
创建上下文的成本很高,因此如果你打算渲染许多图像,最好创建一次上下文并使其保持活动状态。至于过滤器,我们将使用CIFilter.sepiaTone()
默认值,但因为我们稍后会使其灵活,所以我们将使用过滤器@State
,以便可以更改它。
因此,将这两个属性添加到ContentView
:
有了这两个,我们现在可以编写一个方法来处理导入的任何图像——这意味着它将根据中的值设置我们的棕褐色过滤器的强度filterIntensity
,从过滤器中读取输出图像,要求CIContext
渲染它,然后将结果放入我们的image
属性中,以便在屏幕上可见。
提示:可悲的是,棕褐色调滤镜背后的核心图像想要Float
而不是Double
它的值。这让 Core Image 感觉更老了,我知道,但别担心——我们很快就会让它消失!
接下来的工作是改变工作方式loadImage()
。现在分配给image
属性,
但我们不再需要它了。相反,它应该将选择的任何图像发送到棕褐色调过滤器中,然后调用applyProcessing()
使魔法发生。
Core Image 滤镜有一个专用inputImage
属性,让我们可以发送一个CIImage
用于滤镜使用的属性,但通常这会被彻底破坏并导致你的应用程序崩溃——使用setValue()
带有键的滤镜方法要安全得多kCIInputImageKey
。
所以,loadImage()
用这个替换你现有的方法:
如果你现在运行代码,你会看到我们的基本应用程序流程运行良好:我们可以选择一个图像,然后查看应用了棕褐色效果的图像。但是我们添加的那个强度滑块不会做任何事情,即使它绑定到filterIntensity
我们的过滤器正在读取的相同值。
这里发生的事情应该不会太令人惊讶:即使滑块正在更改 filterIntensity
的值
,更改该属性也不会自动applyProcessing()
再次触发我们的方法。相反,我们需要通过告诉 SwiftUI filterIntensity
使用onChange()
.
同样,这些onChange()
修饰符可以在我们的 SwiftUI 视图层次结构中的任何位置,但在这种情况下我做了一些不同的事情:当一个特定的视图负责更改一个值时,我通常会将onChange()
直接添加到该视图,这样我以后就可以清楚地调整视图引发副作用。如果多个视图调整相同的值,或者如果不是很具体是什么在改变值,那么我会在视图的末尾添加修饰符。
无论如何,这里filterIntensity
正在被滑块改变,所以让我们在onChange()
那里添加:
你现在可以继续运行该应用程序,但要注意:尽管 Core Image 在所有 iPhone 上都非常快,但在模拟器中通常非常慢。这意味着你可以尝试确保一切正常,但如果你的代码运行速度与携带沉重购物袋的哮喘蚂蚁一样快,请不要感到惊讶。


