SwiftUI:内容是图像的 ViewModifier
SwiftUI: ViewModifier where content is an Image
我收到错误“Type 'PlayButtonModifier' does not conform to protocol 'ViewModifier'
”,我不明白为什么,更重要的是,我不明白如何正确处理。
我只是尝试为 Image
创建一个 ViewModifier
,这样我就可以在上面使用例如 .resizable()
,它只在 Image
在 ViewModifier
协议中,定义了 Content
的 Typealias
。我天真的想法是这应该有效:
struct PlayButtonModifier: ViewModifier {
typealias Content = Image
func body(content: Content) -> some View {
content
}
}
嗯,不。太容易了。结构的隐式类型别名也会发生同样的事情:
struct PlayButtonModifier: ViewModifier {
func body(content: Image) -> some View {
content
}
}
同样的错误。
这里有什么问题?如何正确?
感谢 Asperi 的评论和讨论,我最终使用了以下代码片段。基本上,它是 ViewModifier
的一个实现,专门用于图像。
protocol ImageModifier {
/// `Body` is derived from `View`
associatedtype Body : View
/// Modify an image by applying any modifications into `some View`
func body(image: Image) -> Self.Body
}
extension Image {
func modifier<M>(_ modifier: M) -> some View where M: ImageModifier {
modifier.body(image: self)
}
}
使用起来很简单:
struct MyImageModifier: ImageModifier {
func body(image: Image) -> some View {
image.resizable().scaledToFit()
}
}
struct MyView: View {
var body: some View {
Image(systemName: "play").modifier(MyImageModifier())
}
}
我不是 100% 满意,因为必须将修饰符定义为 return some View
或 return 和 Image
。这两种情况都有缺点,并没有与 SwiftUI 完美集成。
当将 ImageModifier
定义为 return 和 Image
时,它减少了将图像修改为仅图像特定修饰符(实际上是 resizable()
)的可能性,并且当将其定义为 return some View
我不能链接 ImageModifier
,因为第二个修饰符必须是 ViewModifier
。
在这种情况下,修改特定于特定视图类型,Image
比如说,您可以直接在该视图类型上添加扩展:
extension Image {
func myImageModifier() -> some View {
self
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.clipShape(Circle())
}
}
下面是一个完整的游乐场文本示例。如果您在名为 "Otter.png" 的 playground "Resources" 文件夹中添加一张可爱的水獭图片,您会得到更漂亮的结果:)
import PlaygroundSupport
import SwiftUI
let image = (UIImage(named: "Otter.png") ?? UIImage(systemName: "exclamationmark.square")!)
struct ContentView: View {
var body: some View {
VStack {
Text("hello world")
Image(uiImage: image)
.myImageModifier()
}
}
}
extension Image {
func myImageModifier() -> some View {
self
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.clipShape(Circle())
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
我收到错误“Type 'PlayButtonModifier' does not conform to protocol 'ViewModifier'
”,我不明白为什么,更重要的是,我不明白如何正确处理。
我只是尝试为 Image
创建一个 ViewModifier
,这样我就可以在上面使用例如 .resizable()
,它只在 Image
在 ViewModifier
协议中,定义了 Content
的 Typealias
。我天真的想法是这应该有效:
struct PlayButtonModifier: ViewModifier {
typealias Content = Image
func body(content: Content) -> some View {
content
}
}
嗯,不。太容易了。结构的隐式类型别名也会发生同样的事情:
struct PlayButtonModifier: ViewModifier {
func body(content: Image) -> some View {
content
}
}
同样的错误。
这里有什么问题?如何正确?
感谢 Asperi 的评论和讨论,我最终使用了以下代码片段。基本上,它是 ViewModifier
的一个实现,专门用于图像。
protocol ImageModifier {
/// `Body` is derived from `View`
associatedtype Body : View
/// Modify an image by applying any modifications into `some View`
func body(image: Image) -> Self.Body
}
extension Image {
func modifier<M>(_ modifier: M) -> some View where M: ImageModifier {
modifier.body(image: self)
}
}
使用起来很简单:
struct MyImageModifier: ImageModifier {
func body(image: Image) -> some View {
image.resizable().scaledToFit()
}
}
struct MyView: View {
var body: some View {
Image(systemName: "play").modifier(MyImageModifier())
}
}
我不是 100% 满意,因为必须将修饰符定义为 return some View
或 return 和 Image
。这两种情况都有缺点,并没有与 SwiftUI 完美集成。
当将 ImageModifier
定义为 return 和 Image
时,它减少了将图像修改为仅图像特定修饰符(实际上是 resizable()
)的可能性,并且当将其定义为 return some View
我不能链接 ImageModifier
,因为第二个修饰符必须是 ViewModifier
。
在这种情况下,修改特定于特定视图类型,Image
比如说,您可以直接在该视图类型上添加扩展:
extension Image {
func myImageModifier() -> some View {
self
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.clipShape(Circle())
}
}
下面是一个完整的游乐场文本示例。如果您在名为 "Otter.png" 的 playground "Resources" 文件夹中添加一张可爱的水獭图片,您会得到更漂亮的结果:)
import PlaygroundSupport
import SwiftUI
let image = (UIImage(named: "Otter.png") ?? UIImage(systemName: "exclamationmark.square")!)
struct ContentView: View {
var body: some View {
VStack {
Text("hello world")
Image(uiImage: image)
.myImageModifier()
}
}
}
extension Image {
func myImageModifier() -> some View {
self
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.clipShape(Circle())
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())