SwiftUI技术探究之常用View&Modifiers(下)

1,611 阅读1分钟

本文主要内容

一.Border & 拼写检查 & 自动大写
二.Gradient
三.searchable
四.badge & TabView
五.OnOpenURL 六.interactiveDismissDisab 七.animation

一.Border & 拼写检查 & 自动大写

1.1 Border

  • 边框
TextField("首字母默认大写", text: $str).frame(width: 200, height: 56)
    // 直接设置border和cornerRadius显示会有问题
    // 解决:绘制圆形放到此控件上
    .overlay {
        RoundedRectangle(cornerRadius: 20)
            .stroke(.red, lineWidth: 10)
            .padding(-10)
    }

1.2 拼写检查

  • 自动纠错
TextField("首字母默认大写", text: $str).frame(width: 200, height: 56)
    // 自动纠错:输入是什么就是什么,不进行智能纠错
    .disableAutocorrection(true)

1.3 自动大写

  • 句子首字母大写
  • 单词首字母大写
  • 每个字母都默认大写
  • 不默认大写
TextField("首字母默认大写", text: $str).frame(width: 200, height: 56)
    // sentences:每一句文字首字母大写
    // words:每个单独首字母大写
    // characters:每个字母大写
    // never:不默认大写
    .textInputAutocapitalization(.sentences)

二.Gradient

渐变效果

2.1 角度渐变Angular Gradient

AngularGradient(colors: [Color.red, Color.blue, Color.green, Color.pink, Color.white, Color.red], center: .center)

截屏2022-08-30 10.37.11.png

2.2 椭圆渐变Elliptical Gradient

EllipticalGradient(colors:[Color.blue, Color.green], center: .center, startRadiusFraction: 0.0, endRadiusFraction: 0.5)

截屏2022-08-30 10.36.50.png

2.3 线性渐变Linear Gradient

LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)

截屏2022-08-30 10.37.40.png

2.4 放射渐变Radial Gradient

RadialGradient(colors: [Color.red, Color.blue], center: .center, startRadius: 5, endRadius: 500)

截屏2022-08-30 10.38.04.png

三.searchable

  • 搜索框
  • .searchable(text: $test),必须搭配NavigationView使用
  • 可以添加历史记录

示例:搜索筛选名字

struct DetailView: View, Identifiable {
    var id = UUID()
    var detail: String
    @State var text = ""
    var body: some View {
        Text(detail).font(.largeTitle).foregroundColor(.gray).bold()
            .searchable(text: $text) {
                // 添加历史记录
                Text("苹果").searchCompletion("apple")
                Text("香蕉").searchCompletion("banana")
                Text("梨").searchCompletion("pear")
            }
        Spacer()
    }
}

struct ItemModel: Identifiable {
    var id = UUID()
    var name: String
    var detailView: DetailView
}

let datas: [ItemModel] = [
    ItemModel(name: "Hank", detailView: DetailView(detail: "Hank老师,擅长逆向和跨平台")),
    ItemModel(name: "Lina", detailView: DetailView(detail: "最可爱的客服老师")),
    ItemModel(name: "Cocci", detailView: DetailView(detail: "Cocci老师,擅长底层和塞班")),
    ItemModel(name: "Cat", detailView: DetailView(detail: "Cat老师,非常有责任心的大汉")),
    ItemModel(name: "Zions", detailView: DetailView(detail: "这是我")),
    ItemModel(name: "Kody", detailView: DetailView(detail: "Kody老师,擅长Swift底层"))
]

class ViewModel: ObservableObject {
    @Published var allItem: [ItemModel] = datas
    @Published var searchedItem: String = ""
    
    var filtedItem: [ItemModel] {
        searchedItem.isEmpty ? allItem : allItem.filter({ str in
            str.name.lowercased().contains(searchedItem.lowercased())
        })
    }
}

struct Searchable: View {
    @ObservedObject var vm = ViewModel()
    var body: some View {
        NavigationView {
            List {
                ForEach(vm.filtedItem) { item in
                    NavigationLink(item.name, destination: item.detailView)
                }
            }
            .navigationTitle(Text("搜索页面"))
            .searchable(text: $vm.searchedItem, prompt: "输入您想要搜索的内容")
        }
    }
}

四.badge & TabView

4.1 badge

  • Text的badge
  • 只支持List-Rows和Tabbars
  • 只能设置简单的属性,如font、foregroundColor等,较复杂的属性不起作用
List(0..<50) { i in
    Text("Hello,SwiftUI!")
        .badge(Text("badge").font(.subheadline).bold().foregroundColor(.orange))
}

4.2 TabView

  • 底部栏,类似UIKit中的TabBar
  • 其中只能添加Text和Image
  • 通过设置tabViewStyle属性为page,实现滚动界面(轮播图效果)
TabView {
    Text("The First Tab")
        // 添加角标 
        .badge(10) // 位置1
        .tabItem {
             Image(systemName: "1.square.fill")
             Text("First")
        }.tag(1)
    Text("Another Tab")
        .tabItem {
            Image(systemName: "2.square.fill")
            Text("Second")
        }.tag(2).badge(3) // 位置2
    Text("The Last Tab")
        // badge其中的Text无法修改属性
        .badge(Text("News")) 
        .tabItem {
            Image(systemName: "3.square.fill")
            Text("Third")
        }.tag(3)
}.tabViewStyle(.page).background(.orange) // 轮播图效果
.font(.headline)

截屏2022-08-30 14.27.40.png

截屏2022-08-30 14.34.58.png

五.OnOpenURL

  • 1.配置工程的Info中URL Types,填写URL Schemes(比如Tonly)

截屏2022-08-30 14.50.22.png

  • 2.代码如下:
@State var show = false
@State var tabSelection = 1


TabView {
    Text("The First Tab")
        // 添加角标 
        .badge(10) // 位置1
        .tabItem {
             Image(systemName: "1.square.fill")
             Text("First")
        }.tag(1)
    Text("Another Tab")
        .tabItem {
            Image(systemName: "2.square.fill")
            Text("Second")
        }.tag(2).badge(3) // 位置2
}.onOpenURL { url in
    switch url.host {
    case "Tab1":
        tabSelection = 1
    case "Tab2":
        tabSelection = 2
    default:
        show.toggle()
    }
}
.sheet(isPresented: $show) {
        Text("URL参数错误")
}
  • 3.运行工程,打开浏览器,输入“Tonly://tab1”则可以拉起APP并跳转到对应界面。

截屏2022-08-30 15.31.56.png

  • 当URL错误时,显示错误文案!

截屏2022-08-30 15.35.46.png

六.interactiveDismissDisab

  • 禁止下滑关闭弹出界面
@State var show = false

Button("Open Sheet") {
    show.toggle()
}
.sheet(isPresented: $show) {
    Button("Close") {
        show.toggle()
    }.interactiveDismissDisabled()
}
  • 场景:当前界面有工作还没做完就不小心关闭,当前界面任务已完成,需要关闭但是无法下滑关闭。

struct SetSheetDelegate: UIViewRepresentable {
    let delegate: SheetDelegate

    init(isDisable:Bool, attempToDismiss: Binding<UUID>) {
        self.delegate = SheetDelegate(isDisable, attempToDismiss: attempToDismiss)
    }

    func makeUIView(context: Context) -> some UIView {
        return UIView()
    }

    func updateUIView(_ uiView: UIViewType, context: Context) {
        DispatchQueue.main.async {
            uiView.parentViewController?.presentationController?.delegate = delegate
        }
    }
}

class SheetDelegate: NSObject, UIAdaptivePresentationControllerDelegate {
    var isDisable: Bool
    @Binding var attempToDismiss: UUID

    init(_ isDisable: Bool, attempToDismiss: Binding<UUID> = .constant(UUID())) {
        self.isDisable = isDisable
        _attempToDismiss = attempToDismiss
    }

    func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
        !isDisable
    }

    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        attempToDismiss = UUID()
    }
}

extension View {
    func interactiveDismissDisabled(_ isDisable: Bool, attempToDismiss: Binding<UUID>) -> some View {
        background(SetSheetDelegate(isDisable: isDisable, attempToDismiss: attempToDismiss))
    }
}

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self.next
        while parentResponder != nil {
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
            parentResponder = parentResponder?.next
        }
        return nil
    }
}

struct ContentView: View {

    @State var sheet = false
    @State var showingAlert = false
    @State var disable = true
    @State var attempToDismiss = UUID()

    var body: some View {
        VStack {
            Button("去干大事") {
                sheet.toggle()
            }.font(.title)
        }.frame(maxWidth: .infinity, maxHeight: .infinity).background(.yellow)
        .sheet(isPresented: $sheet) {
            VStack {
                Button(action: {
                    disable.toggle()
                }, label: {
                    VStack {
                        Text("页面事件完成了?\n(数据上传、信息保存等)").padding(.bottom, 40)
                        Text("\(disable ? "没完成" : "完成了")").bold().foregroundColor(disable ? .red : .green)
                    }
                }).font(.title)
                .interactiveDismissDisabled(disable, attempToDismiss: $attempToDismiss)
                .alert(isPresented: $showingAlert) {
                    Alert(title: Text("温馨提示"), message: Text("您还有事件没完成,是否丢弃?"), primaryButton: .default(Text("继续干")), secondaryButton: .destructive(Text("丢弃"), action: {
                        sheet.toggle()
                    }))
                }
            }.frame(maxWidth: .infinity, maxHeight: .infinity).background(.gray.opacity(0.3))
            .onChange(of: attempToDismiss) { _ in
                print("try to dismiss sheet")
                showingAlert.toggle()
            }
        }
    }
}

七.animation

隐士动画

7.1 animation修饰View

类似心跳动画效果

@State var scaleAmount: CGFloat = 1

Button("Animation") {
    scaleAmount += scaleAmount<=1 ? 1 : -1
}
 .font(.title).padding().background(.orange).cornerRadius(20)
 // 心跳动画
 .scaleEffect(scaleAmount).animation(Animation.easeInOut(duration: 3).delay(1).repeatForever(), value: scaleAmount)

凸出内容的动画效果

@State var scaleAmount: CGFloat = 1

Button("Animation") {
    scaleAmount += scaleAmount<=1 ? 1 : -1
}
 .font(.title).padding().background(.orange).cornerRadius(20)
 .overlay {
     RoundedRectangle(cornerRadius: 20)
         .stroke(Color.red)
         .scaleEffect(scaleAmount)
         // 透明度变化
         .opacity(Double(2 - scaleAmount))
         .animation(Animation.easeOut(duration: 1).repeatForever(autoreverses: false), value: scaleAmount)
 }

截屏2022-08-30 17.23.20.png

7.2 animation修饰变量

  • 用到此变量的View都会产生动画
@State var scaleAmount: CGFloat = 1

VStack {
    Button("Animation") {
        scaleAmount += scaleAmount<=1 ? 1 : -1
    }.font(.title).padding().background(.orange).cornerRadius(20).scaleEffect(scaleAmount)
    
    Stepper("Stepper", value: $scaleAmount.animation(
        Animation.easeOut(duration: 1)
            .repeatCount(3, autoreverses: true)
    ), in: 1...10)
    
    Button("Animation") {
        
    }.rotationEffect(.degrees(30 * scaleAmount))
}