SwiftUI 5.0(iOS 17)进一步定制 TipKit 外观让撸码如虎添翼

概览

在之前 SwiftUI 5.0(iOS 17)TipKit 让用户更懂你的 App 这篇博文里,我们已经初步介绍过了 TipKit 的基本知识。

现在,让我们来看看如何进一步利用 SwiftUI 对 TipKit 提供的细粒度外观定制技巧,让 Tip 更加"明眸皓齿"。

在本篇博文中,您将学到如下内容:

  • 概览
  • [1. TipKit 温故而知新](#1. TipKit 温故而知新)
  • [2. Tip 外观细粒度定制](#2. Tip 外观细粒度定制)
  • [3. 完全自己打造 Tip 外观](#3. 完全自己打造 Tip 外观)
  • 总结

相信学完本课后,小伙伴们对 TipKit 的内功修为会更加炉火纯青、登峰造极!

那还等什么呢?Let's go!!!😉


本文对应的视频课在此,欢迎小伙伴们恣意观赏!

SwiftUI 定制 TipKit 外观进一步让撸码如虎添翼


1. TipKit 温故而知新

在前一篇关于 TipKit 的博文中,我们介绍了什么是 TipKit 以及如何在 App 中支持 TipKit 的方法。

这里再回忆一下:创建一个 Tip 很简单,我们只需让类型遵守 Tip 协议即可。

swift 复制代码
struct FavoriteTip: Tip {
    var title: Text {
        Text("收藏最爱的图片")
            .bold()
    }
    
    var message: Text? {
        Text("将心仪的图片保存到相册中")
            .font(.headline)
            .foregroundStyle(.gray.gradient)
    }

	var image: Image? {
        Image(systemName: "heart")
    }
}

然后,我们可以通过嵌入和弹出两种不同方法来显示它:

swift 复制代码
// 嵌入 Tip
struct ContentView: View {
    let favTip = FavoriteTip()
    
    var body: some View {
        NavigationStack {
            VStack {
                
                TipView(favTip)
            }
            .padding()
            .navigationTitle("TitKit演示")
        }
    }
}

// 弹出 Tip
struct ContentView: View {
    let favTip = FavoriteTip()
    
    var body: some View {
        NavigationStack {
            VStack {...}
            .padding()
            .navigationTitle("TitKit演示")
            .toolbar {
                ToolbarItem {
                    Image(systemName: "heart")
                        .font(.title.weight(.black))
                        .foregroundStyle(.pink.gradient)
                        .popoverTip(favTip, arrowEdge: .top)
                }
            }
        }
    }
}

嵌入和弹出 Tip 的效果如下图所示:

如果大家对于默认 Tip 的外观不甚满意怎么办?别急,SwiftUI 还有"妙计"。

2. Tip 外观细粒度定制

Apple 在推出 TipKit 框架的同时也提供了若干修改器方法,我们可以利用它们来进一步调整 Tip 视图的外观。

首先是 tipCornerRadius() 方法,它可以用来调整 Tip 视图边角的弧度:

swift 复制代码
struct ContentView: View {
    let favTip = FavoriteTip()
    @State var addTipRadius = false
    
    var body: some View {
        NavigationStack {
            VStack {
                TipView(favTip)
                
                Toggle(isOn: $addTipRadius) {
                    Text("增加 Tip 边角弧度")
                        .font(.title3)
                }
            }
            .padding()
            
            .navigationTitle("TitKit演示")
            .tipCornerRadius(addTipRadius ? 30 : 10)
            .frame(maxHeight: .infinity)
            .background(.gray.opacity(0.66).gradient)
            .animation(.bouncy, value: addTipRadius)
        }
    }
}

值得注意的是:tipCornerRadius 修改器方法和随后介绍的所有 Tip 外观调整方法都会沿着视图继承树向下传递,这意味着顶层的方法会影响内部所有的 Tip 外观。

接下来是 tipImageSize() 修改器,我们可以用它来调整 Tip 内部图片的尺寸:

swift 复制代码
NavigationStack {
    VStack {
        
        TipView(favTip)

    }
    .tipImageSize(CGSize(width: incImageSize ? 50 : 24, height: incImageSize ? 50 : 24))
    .frame(maxHeight: .infinity)
    .background(.gray.opacity(0.66).gradient)
    .animation(.bouncy, value: incImageSize)
}

运行效果如下所示:

最后,我们可以用 tipBackground() 修改器方法来调整 Tip 视图背景的显示样式:

swift 复制代码
NavigationStack {
    VStack {
        
        TipView(favTip)

    }
    .tipBackground(incBackgroundHue ? Material.ultraThick : .thin)
    .frame(maxHeight: .infinity)
    .background(.gray.opacity(0.66).gradient)
    .animation(.bouncy, value: incImageSize)
}

运行的效果不出意料:

上面我们介绍了一些从宏观上调整 Tip 外观的方法,如果小伙伴们还是觉得捉襟见肘、鸟入樊笼怎么办?

别急,我们还可以"欲穷千里目,更上一层楼":自己动手"丰衣足食"来 100% "恣意"决定 Tip 该如何显示。

3. 完全自己打造 Tip 外观

为了最大程度满足秃头码农们随心所欲定制 Tip 外观的需求,苹果决定将 Tip 的外观样式化(Styling)以便让我们可以无拘无束定制它们"主题皮肤"。

经常撸 SwiftUI 代码的小伙伴们都知道,在 SwiftUI 中大部分原生视图都有对应的样式(Style),比如按钮、Label、Toggle 等等。视图样式为我们充分定制视图的外观带来了极大的便利。

TipKit 也不例外,我们同样可以利用 tipViewStyle() 修改器来排忧解难:

为了能够安闲自在的 100% 纯手工打造 Tip 的外观,我们首先需要"由着性子"创建一个遵守 TipViewStyle 协议的类型:

swift 复制代码
struct PureCustomTipViewStyle: TipViewStyle {
    func makeBody(configuration: Configuration) -> some View {
        VStack(alignment: .leading) {
            HStack(alignment: .bottom) {
                if let image = configuration.image {
                    image
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .font(.title.weight(.heavy))
                        .frame(width: 25, height: 25)
                        .foregroundStyle(.teal)
                        .padding()
                        .overlay {
                            RoundedRectangle(cornerRadius: 15.0)
                                .stroke(.secondary, lineWidth: 3.0)
                        }
                }
                
                Rectangle()
                    .frame(width: 3, height: 100)
                    .foregroundStyle(.white.gradient)
                
                VStack(alignment: .leading) {
                    if let title = configuration.title {
                        title.font(.headline)
                    }
                    
                    if let message = configuration.message {
                        Group {
                            message
                                .font(.subheadline)
                                .padding()
                        }
                        .background {
                            RoundedRectangle(cornerRadius: 15.0)
                                .foregroundStyle(Material.thin)
                        }
                    }
                }
            }
            
            HStack {
                Spacer()
                ForEach(configuration.actions, id: \.id) { action in
                    Button {
                        action.handler()
                    } label: {
                        Image(systemName: "volleyball.fill")
                            .foregroundStyle(.yellow.gradient)
                        action.label()
                    }
                    .fontWeight(.bold)
                    .buttonStyle(.borderedProminent)
                }
            }
        }
        .padding()
        .background {
            Image("bg")
                .resizable()
                .opacity(0.77)
        }
    }
}

接着,为了方便起见我们还可以将上面创建的 Tip 自定义样式融入到 TipViewStyle 自身中去:

swift 复制代码
extension TipViewStyle where Self == PureCustomTipViewStyle {
    static var pureCustom: Self {
        Self.init()
    }
}

最后在 Tip 视图本身或其上层容器中,我们即可悠然自得的调用 tipViewStyle() 修改器方法来施展改头换面的"黑魔法"啦:

swift 复制代码
struct ContentView: View {
    let favTip = FavoriteTip()
    @State var addTipRadius = false
    @State var incImageSize = false
    @State var incBackgroundHue = false
    
    var body: some View {
        NavigationStack {
            VStack {
                
                TipView(favTip)
                
                Toggle(isOn: $addTipRadius) {
                    Text("增加 Tip 边角弧度")
                        .font(.title3)
                }
                
                Toggle(isOn: $incImageSize) {
                    Text("增加 Tip 图片大小")
                        .font(.title3)
                }
                
                Toggle(isOn: $incBackgroundHue) {
                    Text("增加 Tip 背景色度")
                        .font(.title3)
                }
            }
            .padding()
            
            .navigationTitle("TitKit演示")
            .tipCornerRadius(addTipRadius ? 30 : 10)
            .tipImageSize(CGSize(width: incImageSize ? 50 : 24, height: incImageSize ? 50 : 24))
            .tipBackground(incBackgroundHue ? Material.ultraThick : .thin)
            // 应用我们的自定义 Tip 样式
            .tipViewStyle(.pureCustom)
            .frame(maxHeight: .infinity)
            .background(.gray.opacity(0.66).gradient)
            .animation(.bouncy, value: addTipRadius)
            .animation(.bouncy, value: incImageSize)
            .animation(.bouncy, value: incBackgroundHue)
        }
    }
}

编译运行代码,现在用全身毛孔来感受一下纯手工打造 Tip 界面的愉悦之美吧!

至此,我们完全掌握了 TipKit 中外观调整与定制的全部技巧,小伙伴们还不赶快发挥天马行空般的想象力打造自己的 Tip 视图吧!棒棒哒!💯


想要系统学习 Swift 语言的小伙伴们,千万不要错过我的《Swift 语言开发精讲》专栏哦,欢迎大家恣意观赏:


总结

在本篇博文中,我们介绍了 SwiftUI 5.0 中从宏观全局调整 Tip 视图显示的几种方式。如果小伙伴们觉得还是不能放开手脚,我们还探讨了如何 100% 纯手工打造自己 Tip 内部布局的方法,包您满意!

感谢观赏,再会!😎

相关推荐
大熊猫侯佩4 个月前
SwiftUI 5.0(iOS 17)滚动视图的滚动目标行为(Target Behavior)解惑和实战
滚动·scrollview·swiftui 5.0·ios 17·target behavior·惰性容器·滚动对齐
大熊猫侯佩7 个月前
SwiftUI 5.0(iOS 17.0)触摸反馈“震荡波”与触发器模式趣谈
触发器·swiftui 5.0·trigger·ios 17.0·haptic·震动反馈·sensoryfeedback
大熊猫侯佩1 年前
Swift 5.9 与 SwiftUI 5.0 中新 Observation 框架应用之深入浅出
swift 5.9·swiftui 5.0·observation·observable·bindable·可观察对象·可变对象