美文网首页SwiftUI - 基础到实战
SwiftUI - 常用Views / Modifies(二)

SwiftUI - 常用Views / Modifies(二)

作者: Lcr111 | 来源:发表于2023-02-22 20:49 被阅读0次
1、Gradient

渐变效果有四种,AngularGradient(时钟样式渐变)EllipticalGradient(椭圆渐变)LinearGradient(线性渐变)RadialGradient(雷射渐变),都可以直接在右上角添加视图中找到:

//startPoint endPoint 控制渐变方向,是从上往下还是从左往右,还是左上到右下
LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue, .red]), startPoint: .top, endPoint: .bottom)
//colors 从内向外发射渐变,最后一个是背景色
//startRadius 控制第一个颜色的半径
//endRadius 控制倒数第二个半径,中间的颜色均匀分布渐变效果
RadialGradient(gradient: Gradient(colors: [Color.red, Color.blue, .yellow, .purple]), center: .center, startRadius: 10, endRadius: 150)

其中的.center是设置渐变从起始点(中心点)的位置,可以在代码中试试。

2、Badge / TabView

看到Badge,就能想到是和Tabbar小图标相关的,SwiftUI里的Badge除了可以作为底部Tabbar小图标外,还能在List中使用,如下所示:

List(0..<100){ i in
      Text("Hello World").badge(2)
}
Badge
右侧展示的就是badge,可用作未读消息条数展示功能。另外不只是展示数字,也可以展示一些简单的自定义Text:
List(0..<100){ i in
      Text("Hello World").badge(Text("111").font(.largeTitle)
       .foregroundColor(.orange).bold())
}

TabView就是之前的Tabbar,而之前的UITableView在SwiftUI里面被List替代了,这两个不要搞混了。

TabView() {
       Text("Tab1").badge(3).tabItem {
             Image(systemName: "person")
             Text("Tab1")
       }.tag(1)
       Text("Tab2").tabItem {
               Image(systemName: "circle")
               Text("Tab2")
       }.tag(2)
 }

如果给TabView添加.tabViewStyle(.page)之后,TabView就变成了类似UIScrollView的左右滑动轮播图的效果,而且还是自带UIPageControll小圆点,如果想隐藏小圆点的话加上.page(indexDisplayMode: .never)就行了。TabView具体的使用细节以后项目中再细说...

3、OnOpenUR

用作浏览器跳转到此App的链接,可以根据链接跳转到目标界面,紧接上面的TabView:

@State var selectionIndex = 1
@State var show = false

TabView(selection: $selectionIndex) {
                Text("Tab1").badge(3).tabItem {
                    Image(systemName: "person")
                    Text("Tab1")
                }.tag(1)
                Text("Tab2").tabItem {
                    Image(systemName: "circle")
                    Text("Tab2")
                }.tag(2)
            }
            .onOpenURL { url in
                switch url.host {
                case "Tab1":
                    selectionIndex = 1
                case "Tab2":
                    selectionIndex = 2
                default:
                    show.toggle()
                    
                }
            }
            .sheet(isPresented: $show) {
                Text("参数错误")
            }

info里面在URL Types中添加外链host(Lcr):

添加URL
运行项目,打开浏览器输入Lcr://Tab1即可进入App的Tab1界面,输入Lcr://Tab2即可进入Tab2界面,如果输入Lcr://Tab3就会跳出报错弹窗。
拓展
  • URL 删除的话可以在info.plist文件中删除,然后重启项目就看不到刚刚添加的URL Types了。
  • interactiveDismissDisab上面例子中的参数错误sheet为presentViewController,自带下滑隐藏功能,如果需要关闭此功能,可以添加interactiveDismissDisab()即可:
Text("参数错误").interactiveDismissDisabled()
4、Animation

SwiftUI里动画写法很简洁,代码量少,此处我们以scaleEffect为示例:

@State var scaleAmount: CGFloat = 1
//scaleEffect 缩放动画
//easeInOut 动画方式 如深入浅出
//repeatForever 重复动画
//onAppear 初始化为2时会自动开始动画,如果为1是否已经动画需要再研究,视觉上是没有
Button("animation"){
           scaleAmount += scaleAmount <= 1 ?1 : -1
}.font(.title).padding().background(.green).cornerRadius(20)
.scaleEffect(scaleAmount).animation(Animation.easeInOut(duration: 3).repeatForever(), value: scaleAmount)
 .onAppear {
        scaleAmount = 2
  }

就能实现一个绿色按钮放大缩小的动画。
如果动画效果不需要原路返回,加上.repeatForever(autoreverses: false)就可以了。

5、searchale

searchable就是我们经常用的UISearchBar+UISearchController,下面我们利用一个例子来简单用一下:

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

struct DetailView: View, Identifiable {
    var id = UUID()
    var detail: String
    var body: some View {
        Text(detail).font(.largeTitle).foregroundColor(.gray).bold()
    }
}

let datas: [ItemModel] = [
    ItemModel(name: "Tom", detailView: DetailView(detail: "Tom喜欢吃苹果")),
    ItemModel(name: "Jim", detailView: DetailView(detail: "Jim喜欢吃香蕉")),
    ItemModel(name: "Lily", detailView: DetailView(detail: "Lily喜欢吃梨")),
    ItemModel(name: "Lucy", detailView: DetailView(detail: "Lucy喜欢吃橘子")),
]

class ViewModel: ObservableObject {
    @Published var allItems: [ItemModel] = datas
    @Published var searchString: String = ""
    
    var filteredItems: [ItemModel] {
        searchString.isEmpty ? allItems :
        allItems.filter({ item in
            item.name.lowercased().contains(searchString.lowercased())
        })
    }
    
}

struct ContentView: View {
    @ObservedObject var vm = ViewModel()
    var body: some View {
        NavigationView{
            List{
                ForEach(vm.filteredItems) { item in
                    NavigationLink(item.name, destination: item.detailView)
                }
            }
            .navigationTitle(Text("搜索页面"))
            .searchable(text: $vm.searchString,prompt: "输入你想搜索的名字")
        }
    }
}

运行效果如下,当输入名字时筛选出适合的数据:


Searchable
6、NavigationView / NavigationLink

NavigationView导航栏,上方搜索示例中,List需要放入NavigationView中才有效,因为搜索框是和NavigationView绑定的,和之前的UISearchBar类似。

//第二界面界面
struct DetailView: View {
    var body: some View {
        //此处不用再包一层NavigationView
        VStack {
            //前往详情界面
            NavigationLink("detail"){
                Text("Detail")
            }
        }
    }
}
//第一界面
var body: some View {
        NavigationView {
            NavigationLink("look detail"){
                DetailView()
            }
        }.navigationTitle("navi").navigationBarTitleDisplayMode(.inline)
}

NavigationLink可以理解为一种点击可以跳转界面的控件。
有一点让我不理解的是,上方搜索示例中的.navigationTitle和.searchable为什么不是加在NavigationView上,而是加在List上的。在查看NavigationView源码后,源码中也有使用示例,我猜可能是NavigationView就是起一个包装容器作用吧,相关的title、样式设置放在了第一层子View上。
注意:NavigationView只需要在最外层写一个就好,内层界面不用再写

7、DatePicker

日期选择器,样式比之前的系统自带的样式好看多了,操作也方便:

@State var date = Date()
    
    let dateRange: ClosedRange<Date> = {
        let calender = Calendar.current
        let startComponents = DateComponents(year: 2021, month:1, day: 1)
        let endComponents = DateComponents(year: 2021, month:12, day:31, hour:23, minute:59, second:59)
        return calender.date(from: startComponents)!
        ...
        calender.date(from: endComponents)!
    }()

//selection 当前选择的日期时间
//in  范围,设定只能在这个范围内选择
//displayedComponents 模式,日期事件的模式
DatePicker(selection: $date, in: dateRange, displayedComponents: [.date, .hourAndMinute]) {
      //Text("\(date.description)")            
}
  //.frame(width: 200,height: 50)

加上选择的范围后,就不能选择范围外的日期及时间。
ClosedRange 不可数的一个范围,SwiftUI里提供了四种表示范围的结构:

  • CountableClosedRange : 可数的一个闭区间范围 支持for循环
  • CountableRange :可数的一个开区间范围 支持for循环
  • ClosedRange :不可数 不支持for 循环
  • Range :不可数 不支持for循环
8、contextMenu / Menu

一种弹窗式的选择器,类似于微信右上角点击➕的PopView

@State var backgroundColor = Color.red
@State var isShow = true

Text("Hello Lcr").bold().font(.largeTitle).foregroundColor(.white).background(backgroundColor)
                .contextMenu(isShow ? ContextMenu{
                    Button("Red"){
                        backgroundColor = .red
                    }
                    Button("Blue"){
                        backgroundColor = .blue
                    }
                    Button("Yellow"){
                        backgroundColor = .yellow
                    }
                    Button("Green"){
                        backgroundColor = .green
                    }
                } : nil)

长按Text,会弹出选择器,切换Text背景颜色,不妨动手试试吧。选项的显示顺序,从上往下排列。

Menu功能和样式上也是和contextMenu一样的,但单击就行,不需长按,写法也简便点。

Menu("Menu"){
                Text("选项一")
                Text("选项二")
                Text("选项三")
                Button("Blue"){
                    backgroundColor = .blue
                }
                Button("Yellow"){
                    backgroundColor = .yellow
                }
                Button("Green"){
                    backgroundColor = .green
                }
            }.font(.largeTitle)

Menu选项的显示顺序,从下往上排列。Menu可以无限嵌套。

9、Map

地图的使用很简单,导入MapKit框架,把传统的MKMapView转化成SwiftUI里的View,那就需要自定义的MapView遵循UIViewRepresentable协议,实现相关方法:

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
        let mapView = MKMapView()
        let locationManager = CLLocationManager()
        mapView.delegate = context.coordinator
        mapView.userTrackingMode = .followWithHeading
        locationManager.requestAlwaysAuthorization()
        return mapView
    }
    func updateUIView(_ view: MKMapView, context: UIViewRepresentableContext<MapView>) {
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

class Coordinator: NSObject, MKMapViewDelegate {
    var parent: MapView
    init(_ parent: MapView) {
        self.parent = parent
    }
    func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
        print("----", mapView.centerCoordinate)
    }
    func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
        mapView.centerCoordinate = userLocation.coordinate
    }
}

struct ContentView: View {
    var body: some View {
        MapView().ignoresSafeArea()
    }
}

协议中的makeUIView方法返回一个MKMapView对象。
如果需要用到地图的相关代理方法,就要添加代理类Coordinator去遵守MKMapViewDelegate协议,然后设置代理及在makeCoordinator方法将二者绑定。

10、List

SwiftUI里List 替代了UITableView,结构上也模仿了UITableView及Cell之间的结构,使用起来效率是真的高,代码量少:

List{
       ForEach(0..<100){
            Text("Cell \($0)").listRowSeparator(.hidden)
       }
}.font(.largeTitle).listStyle(.plain)

几句代码就完成了一个列表的创建。
分组用法如下:

List{
       ForEach(0..<3) { _ in
            Section {
                      ForEach(0..<6){
                          Text("Cell \($0)")
                              .listRowSeparator(.hidden)
                      }
            } header: {
                    Text("Header").foregroundColor(.white)
                          .padding().background(.blue)
                    }
        }
}.font(.largeTitle).listStyle(.grouped)

即可创建分组List,.listStyle为列表展示样式,当设置为.listStyle(.plain)时,组头可悬停在顶部;当设置.listStyle(.sidebar)时每一组都可以收缩及放开,很方便。
如果需要给每个“Cell”添加操作比如删除,那就需要给"Cell"的内容添加.swipeActions():

//edge 设置按钮的位置,是左侧还是右侧滑出
//allowsFullSwipe 滑动的距离长短区别
Text("Cell \($0)")
          .listRowSeparator(.hidden)
          .swipeActions(edge: .trailing, allowsFullSwipe: true) {
                Button("delete"){
                       print("delete")
                }
          }

List默认自带顶部刷新功能(底部加载功能暂时没有提供),给List添加.refreshable()即可满足一般的刷新需求,如果想要加文字图片啥的就需要自定义了。

.refreshable {
      print("refresh")
 }

渐渐接触到的控件也越来越多了,这些都是做好项目必经之路,打好基础才能搭建高楼大厦,我们未完待续!!!

相关文章

网友评论

    本文标题:SwiftUI - 常用Views / Modifies(二)

    本文链接:https://www.haomeiwen.com/subject/ycqakdtx.html