四、SwiftUI的交互操作

83 阅读3分钟

1、添加单击手势 -- .onTapGesture || TapGesture

import SwiftUI

struct ContentView : View
{
    @State var isPressed = false
    
    var body: some View
    {
        let tapGesture = TapGesture().onEnded
        { _ in
            self.isPressed.toggle()
        }
        
        return Circle()
            .fill(Color.orange)
            .frame(width: 240, height: 240)
            .scaleEffect(isPressed ? 1.5 : 1)
            .animation(.default)
//          .onTapGesture {  
//          }
            .gesture(tapGesture)
    }
}

2、添加双击手势 -- onTapGesture(count: 2)

import SwiftUI

struct ContentView : View
{
    @State var isPressed = false
    
    var body: some View
    {
        Circle()
            .fill(Color.orange)
            .frame(width: 240, height: 240)
            .scaleEffect(isPressed ? 1.4 : 1)
            .animation(.default)
            .onTapGesture(count: 2)
            {
                self.isPressed.toggle()
            }
    }
}

3、添加长按手势 -- LongPressGesture

import SwiftUI

struct ContentView : View
{
    @GestureState var isLongPressing = false
    @State var isLongPressed = false
    
    var body: some View
    {
        let longPressGesture = LongPressGesture()
            .updating($isLongPressing)
            { value, state, transcation in
                print(value, state, transcation)
                state = value
            }
            .onEnded { (value) in
                print(value)
                self.isLongPressed = true
            }
        
        return Circle()
            .fill(Color.orange)
            .frame(width: 240, height: 240)
            .scaleEffect(isLongPressed ? 1.4 : 1)
            .animation(.default)
            .gesture(longPressGesture)
    }
}

4、添加旋转手势 -- RotationGesture

import SwiftUI

struct ContentView : View
{
    @State var angle = 0.0
    
    var body: some View
    {
        let rotationGesture = RotationGesture(minimumAngleDelta: Angle.init(degrees: 20))
            .onChanged({ (angle) in
                self.angle = angle.degrees
            })
            .onEnded { (angle) in
                print(self.angle)
            }
        
        return Image("couples")
            .rotationEffect(Angle.init(degrees: self.angle))
            .gesture(rotationGesture)
    }
}

5、添加拖动手势 -- DragGesture

import SwiftUI

struct ContentView : View
{
    @State var offset: CGSize = .zero
    
    var body: some View
    {
        let dragGesture = DragGesture()
            .onChanged { (value) in
                print(value.startLocation, value.location, value.translation)
                self.offset = value.translation
            }
            .onEnded { (value) in
                if(abs(value.translation.width) >= 40 ||
                   abs(value.translation.height - (-260)) >= 40)
                {
                    self.offset = .zero
                }
                else
                {
                    self.offset = CGSize(width: 0, height: -260)
                }
            }
        
        return VStack
        {
            Circle()
                .fill(Color.black)
                .opacity(0.1)
                .frame(width: 200, height: 200)
                .offset(CGSize(width: 0, height: -50))
            
            Circle()
                .fill(Color.orange)
                .frame(width: 200, height: 200)
                .offset(offset)
                .gesture(dragGesture)
                .animation(.spring())
        }
    }
}

6、添加多种组合手势 -- simultaneously

import SwiftUI

struct ContentView : View
{
    @State var offset: CGSize = .zero
    @GestureState var isLongPressing = false
    @State var isLongPressed = false
    
    var body: some View
    {
        let longPressGesture = LongPressGesture()
        .updating($isLongPressing)
        { value, state, transcation in
            print(value, state, transcation)
            state = value
        }
        .onEnded
        { (value) in
            print(value)
            self.isLongPressed = true
        }
        
        let dragGesture = DragGesture()
        .onChanged
        { (value) in
            print(value.startLocation, value.location, value.translation)
            self.offset = value.translation
        }
        .onEnded
        { (value) in
            if(abs(value.translation.width) >= 40
                || abs(value.translation.height - (-220)) >= 40)
            {
                self.offset = .zero
            }
            else
            {
                self.offset = CGSize(width: 0, height: -220)
            }
        }
        .simultaneously(with: longPressGesture)
        
        return VStack
        {
            Circle()
                .fill(Color.black)
                .opacity(0.1)
                .frame(width: 200, height: 200)
                .offset(CGSize(width: 0, height: -50))
            
            Circle()
                .fill(Color.orange)
                .frame(width: 200, height: 200)
                .offset(offset)
                .gesture(dragGesture)
                .scaleEffect(isLongPressed ? 1.2 : 1)
                .animation(.spring())
        }
    }
}

7、添加序列手势 -- SequenceGesture

import SwiftUI

struct ContentView : View
{
    @State var offset: CGSize = .zero
    
    var body: some View
    {
        let longPressGesture = LongPressGesture()
            .onEnded
            { (value) in
                print("First gesture.")
            }
        
        let dragGesture = DragGesture()
            .onChanged
            { (value) in
                print(value.startLocation, value.location, value.translation)
                self.offset = value.translation
            }
            .onEnded
            { (value) in
                if(abs(value.translation.width) >= 40
                    || abs(value.translation.height - (-260)) >= 40)
                {
                    self.offset = .zero
                }
                else
                {
                    self.offset = CGSize(width: 0, height: -260)
                }
            }
        
        let sequenceGesture = SequenceGesture(longPressGesture, dragGesture)
            .onEnded
            { _ in
                print("-- SequenceGesture end.")
            }
        
        return VStack
        {
            Circle()
                .fill(Color.black)
                .opacity(0.1)
                .frame(width: 200, height: 200)
                .offset(CGSize(width: 0, height: -50))
            
            Circle()
                .fill(Color.orange)
                .frame(width: 200, height: 200)
                .offset(offset)
                .animation(.spring())
                .gesture(sequenceGesture)
        }
    }
}

8、sizeCategory预览不同字体下的文本视图 -- .environment(.sizeCategory, .extraSmall)

image.png

import SwiftUI

struct ContentView : View
{
    var body: some View
    {
        VStack
        {
            Text("Dynamic Type sizes")
                .font(.system(size: 36))
            
            Text("Dynamic Type sizes")
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider
{
    static var previews: some View
    {
        VStack
        {
            Spacer()
            
            ContentView()
                .environment(\.sizeCategory, .extraSmall)
            
            Spacer()
            
            ContentView()
            
            Spacer()
            
            ContentView()
               .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
            
            Spacer()
        }
    }
}
#endif

9、动态调整字号、视图间距 -- @ScaledMetric

@ScaledMetric:被@ScaledMetric修饰的属性,当系统的Dynamic Type size发生变化时,属性的值也会发生变化。

注意: 调试时需要开启Dynamic Type size

image.png

import SwiftUI

struct ContentView: View
{
    @ScaledMetric var iconSize: CGFloat = 60
    @ScaledMetric private var padding: CGFloat = 10.0

    var body: some View
    {
        VStack
        {
            Image(systemName: "hare")
                .resizable()
                .frame(width: iconSize, height: iconSize)
            
            Text("A rabbit")
                .font(.system(size: 32))
                
            Text("\(Image(systemName: "eurosign.circle"))  38")
                .font(.system(size: 17))
        }
        .padding(padding)
        .background(Color.orange)
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider
{
    static var previews: some View
    {
        ContentView()
            .environment(\.sizeCategory, .extraExtraExtraLarge)
    }
}
#endif

10、在预览窗口使用不同的模拟器预览用户界面 -- previewDevice & previewDisplayName

image.png

import SwiftUI

struct ContentView : View
{
    var body: some View
    {
        VStack
        {
            Image("couples")
            
            Text("Two items of the same kind")
                .font(.system(size: 30))
            
            Text("The couple were married last week. Only one couple left on the dance floor.")
        }
        .padding()
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider
{
    static var previews: some View
    {
        Group
        {
            ContentView()
               .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Max"))
                .previewDisplayName("Device-Max")
            
            ContentView()
                .previewDevice(PreviewDevice(rawValue: "iPhone 12 mini"))
                .previewDisplayName("Device-Mini🍎")
        }
    }
}
#endif

11、正常模式和黑暗模式 -- .environment(.colorScheme, .light)

image.png

import SwiftUI

struct ContentView : View
{
    var body: some View
    {
        VStack(alignment: .center, spacing: 20)
        {
            Text("Dynamic Type sizes")
                .font(.system(size: 48))
                .foregroundColor(Color.secondary)
            
            Text("Dynamic Type sizes")
                .foregroundColor(Color.accentColor)
            
            Image(systemName: "star.fill")
                .foregroundColor(Color.secondary)
                .font(.system(size: 64))
            
            Image("couples")
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .background(Color.black)
        .edgesIgnoringSafeArea(.all)
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider
{
    static var previews: some View
    {
        HStack(spacing:0)
        {
            ContentView()
                .environment(\.colorScheme, .light)
            
            ContentView()
               .environment(\.colorScheme, .dark)
        }
    }
}
#endif

12、上下文菜单 -- contextMenu

image.png

import SwiftUI

struct ContentView : View
{
    var body: some View
    {
        VStack(spacing: 20)
        {
            Image(systemName: "gear")
                .font(.system(size: 64))
            
            Text("Settings")
                .font(.system(size: 14))
        }
        .contextMenu
        {
            VStack
            {
                Button(action: {
                    print("Leave a message.")
                }){
                    Text("Leave a message")
                    Image(systemName: "message")
                }
                
                Button(action: {})
                {
                    Text("Rate the app")
                    Image(systemName: "star")
                }
            }
        }
    }
}

13、向用户推荐其他应用 -- StoreKit & appStoreOverlay

image.png

import SwiftUI
import StoreKit

struct ContentView: View
{
    @State var showAppStoreOverlay = false
    
    var body: some View
    {
        Button(action: {
            self.showAppStoreOverlay.toggle()
        }, label: {
            Label("Show the app in app store", systemImage: "gift")
        })
        .appStoreOverlay(isPresented: self.$showAppStoreOverlay)
        {
            SKOverlay.AppConfiguration(appIdentifier: "1063100471", position: .bottomRaised)
        }
    }
}

14、将文档导出到iCloud -- UniformTypeIdentifiers、FileDocument & fileExporter

image.png

import SwiftUI
import UniformTypeIdentifiers

struct TextDocument: FileDocument
{
    static var readableContentTypes: [UTType]
    {
        [.plainText]
    }
    
    var content: String

    init(content: String)
    {
        self.content = content
    }
    
    init(filePath: String)
    {
        self.content = try! String(contentsOfFile:filePath, encoding: String.Encoding.utf8)
    }
    
    init(configuration: ReadConfiguration) throws
    {
        guard let data = configuration.file.regularFileContents,
              let value = String(data: data, encoding: .utf8)
        else
        {
            throw CocoaError(.fileReadCorruptFile)
        }
        content = value
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper
    {
        return FileWrapper(regularFileWithContents: content.data(using: .utf8)!)
    }
}

struct ContentView: View
{
    @State private var document = TextDocument(content: "Interactive tutorials!")
    @State private var document2 = TextDocument(filePath: Bundle.main.path(forResource:"demoText", ofType:"txt")!)
    @State private var isPresented: Bool = false
    
    var body: some View
    {
        Button(action: {
            self.isPresented.toggle()
        }, label: {
            Label("Export the text file", systemImage: "paperplane")
        })
        .fileExporter(
            isPresented: $isPresented,
            document: document,
            contentType: UTType.plainText,
            defaultFilename: "demoText"
        ) { result in
            if case .success = result {
                print("Success.")
            }
            else {
                print("Failure.")
            }
        }
    }
}

15、实现点餐功能 -- DisclosureGroup

image.png

import SwiftUI

struct ContentView : View
{
    let foodArray = ["Legumes", "Edible plants", "Baked goods", "Breads", "Dairy products", "Eggs", "Meat", "Cereals", "Rice", "Other", "Seafood"]
    @State var visible = false
    @State var food = ""
    
    var body: some View
    {
        VStack
        {
            Text("Your choose: \(food)")
            
            DisclosureGroup("Choose food", isExpanded: self.$visible)
            {
                ScrollView
                {
                    LazyVStack(alignment: .leading, spacing: 20, pinnedViews: /*@START_MENU_TOKEN@*/[]/*@END_MENU_TOKEN@*/, content:
                    {
                        ForEach(foodArray, id: \.self)
                        { item in
                            Text("* \(item)")
                                .onTapGesture
                                {
                                    self.food = "\(item)"
                                    self.visible.toggle()
                                }
                        }
                    })
                    .padding()
                }
                .frame(height: 200)
            }
            .padding()
            .background(Color.gray.opacity(0.4))
            .cornerRadius(10)
        }
        .padding()
    }
}

16、对输入字符串进行过滤 -- onChange

image.png

import SwiftUI

struct ContentView : View
{
    private let foodArray = ["Legumes", "Edible plants", "Baked goods", "Breads", "Salads", "Dairy products", "Sauces", "Eggs", "Meat", "Cereals", "Rice", "Other", "Seafood", "Sandwiches"]
    @State var keyword = ""
    @State var filtedFood : [String] = []
    
    var body: some View
    {
        VStack
        {
            TextField("Choose food", text: $keyword)
                .onChange(of: self.keyword, perform:
                { value in
                    self.filtedFood = self.foodArray.filter({
                        (food) -> Bool in
                        if self.keyword != "" && food.hasPrefix(self.keyword)
                        {
                            return true
                        }
                        else
                        {
                            return false
                        }
                    })
                })
            
            LazyVStack(alignment:.leading, content:
            {
                ForEach(filtedFood, id: \.self)
                { item in
                    Text("* \(item)")
                        .onTapGesture
                        {
                            self.keyword = item
                        }
                }
            })
        }
        .font(.system(size: 32))
        .padding()
    }
}