Sample Code

注: 2022年,SwiftUI 4.0时代,有些代码已经过时咯~

渐变进度条

ZStack(alignment: .leading) {
    Capsule()
        .fill(.tertiary)
        .foregroundStyle(.white)
    GeometryReader { proxy in
        Capsule()
            .fill(.linearGradient(.init(colors: [.orange, .red]), startPoint: .leading, endPoint: .trailing))
            .frame(width: (cast.farenheit / 140) * proxy.size.width)
    }   
}
.frame(height:4)

$visibility.animation(.linear(duration: 5))

Toggle(isOn: $visibility.animation(.linear(duration: 5))) {
    Text("Toggle Text Views")
}

.gesture(nil)

// A gesture recognizer can be removed from a view by passing a nil value to the gesture() modifier
// 吃掉手势事件的方法
.gesture(nil)

refreshable

ScrollView {
    Text("hello world.")
}
.refreshable {
    
}

.push(from: .trailing)

Text("hello world.")
    .transition(.push(from: .trailing))

AnyLayout

struct ContentView: View {
    @State private var show: Bool = false
    var body: some View {
        ZStack {
            let dynamicLayout = show ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
            dynamicLayout {
                Text("hello")
                Text("world")
            }
            .onTapGesture {
                withAnimation(.easeInOut(duration: 1)) {
                    show.toggle()
                }
            }
        }
    }
}
struct ContentView: View { //AnyLayout 另一个例子(官方)
    @Environment(\.dynamicTypeSize) var dynamicTypeSize
    
    var body: some View {
        let layout = dynamicTypeSize <= .medium ?
        AnyLayout(HStack()) : AnyLayout(VStack())
        
        layout {
            Text("First label")
            Text("Second label")
        }
    }
}

.contentTransition(.interpolate)

Text("hello world.")
    .font(show ? .largeTitle : .body)
    .fontWeight(show ? .black : .thin)
    .foregroundColor(show ? .yellow : .red)
    .contentTransition(.interpolate)
    .onTapGesture {
        withAnimation(.easeInOut(duration: 1)) {
            show.toggle()
        }
    }

.toolbar(.hidden, for: .navigationBar)

NavigationStack {
    Text("hello world.")
        .navigationTitle("Title")
        .toolbarBackground(Color.yellow, for: .navigationBar)
        .toolbar(.hidden, for: .navigationBar)
}

MagnificationGesture

struct ContentView: View {
    @State private var magnification: CGFloat = 1.0
    
    var body: some View {
        let magnificationGesture =
        MagnificationGesture(minimumScaleDelta: 0)
            .onChanged({ value in
                self.magnification = value
            })
            .onEnded({ _ in
                print("Gesture Ended")
            })
        
        return Image(systemName: "hand.point.right.fill")
            .resizable()
            .font(.largeTitle)
            .scaleEffect(magnification)
            .gesture(magnificationGesture)
            .frame(width: 100, height: 90)
    }
}
struct MagnificationGestureView: View {
    @GestureState var magnifyBy = 1.0

    var magnification: some Gesture {
        MagnificationGesture()
            .updating($magnifyBy) { currentState, gestureState, transaction in
                gestureState = currentState
            }
    }

    var body: some View {
        Circle()
            .frame(width: 100, height: 100)
            .scaleEffect(magnifyBy)
            .gesture(magnification)
    }
}

transition

.transition(.scale)
.transition(.move(edge: .top))
.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
.transition(.fadeAndMove)
.transition(.asymmetric(insertion: .scale, removal: .slide))

extension AnyTransition {
    static var fadeAndMove: AnyTransition {
        AnyTransition.opacity.combined(with: .move(edge: .top))
    }
}

ToggleStyle

struct ContentView: View {
    @State private var active: Bool = false
    
    var body: some View {
        Toggle(isOn: $active, label: {
            Text("Active")
        })
        .toggleStyle(PowerToggleStyle())
    }
}

struct CheckmarkToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            Rectangle()
                .foregroundColor(configuration.isOn ? .green : .gray)
                .frame(width: 51, height: 31, alignment: .center)
                .overlay(
                    Circle()
                        .foregroundColor(.white)
                        .padding(.all, 3)
                        .overlay(
                            Image(systemName: configuration.isOn ? "checkmark" : "xmark")
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .font(Font.title.weight(.black))
                                .frame(width: 8, height: 8, alignment: .center)
                                .foregroundColor(configuration.isOn ? .green : .gray)
                        )
                        .offset(x: configuration.isOn ? 11 : -11, y: 0)
                        .animation(Animation.linear(duration: 0.1), value: configuration.isOn)
                    
                ).cornerRadius(20)
                .onTapGesture { configuration.isOn.toggle() }
        }
    }
}

struct PowerToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            Rectangle()
                .foregroundColor(configuration.isOn ? .green : .gray)
                .frame(width: 51, height: 31, alignment: .center)
                .overlay(
                    Circle()
                        .foregroundColor(.white)
                        .padding(.all, 3)
                        .overlay(
                            GeometryReader { geo in
                                Path { p in
                                    if !configuration.isOn {
                                        p.addRoundedRect(in: CGRect(x: 20, y: 10, width: 10.5, height: 10.5), cornerSize: CGSize(width: 7.5, height: 7.5), style: .circular, transform: .identity)
                                    } else {
                                        p.move(to: CGPoint(x: 51/2, y: 10))
                                        p.addLine(to: CGPoint(x: 51/2, y: 31-10))
                                    }
                                }.stroke(configuration.isOn ? Color.green : Color.gray, lineWidth: 2)
                            }
                        )
                        .offset(x: configuration.isOn ? 11 : -11, y: 0)
                        .animation(Animation.linear(duration: 0.1), value: configuration.isOn)
                    
                ).cornerRadius(20)
                .onTapGesture { configuration.isOn.toggle() }
        }
    }
}

struct ImageToggleStyle: ToggleStyle {
    var onImageName: String
    var offImageName: String
    
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            Image(configuration.isOn ? onImageName : offImageName)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 51, height: 31, alignment: .center)
                .overlay(
                    Circle()
                        .foregroundColor(.white)
                        .padding(.all, 3)
                        .offset(x: configuration.isOn ? 11 : -11, y: 0)
                        .animation(Animation.linear(duration: 0.1), value: configuration.isOn)
                ).cornerRadius(20)
                .onTapGesture { configuration.isOn.toggle() }
        }
    }
}

ViewModifier

struct ContentView: View {
    var body: some View {
        Text("Active")
            .standardTitle()
    }
}

struct StandardTitle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.largeTitle)
            .padding()
            .border(Color.gray, width: 2)
            .background(Color.white
                .shadow(color: Color.black, radius: 5, x: 0, y: 5))
    }
}

extension View {
    func standardTitle() -> some View {
        self.modifier(StandardTitle())
    }
}

@ViewBuilder

struct MyVStack<Content: View>: View {
    let content: () -> Content
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        VStack(spacing: 10) {
            content()
        }
        .font(.largeTitle)
    }
}

horizontalSizeClass

struct ContentView: View {
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var body: some View {
        if horizontalSizeClass == .compact {
            VStack {
                Text("竖屏")
                Text("shu ping")
            }
        } else {
            HStack {
                Text("横屏")
                Text("heng ping")
            }
        }
    }
}

@AppStorage

struct UserName: Encodable, Decodable {
    var firstName: String
    var secondName: String
}

var username = UserName(firstName: "Mark", secondName: "Wilson")

@AppStorage("username") var namestore: Data = Data()

let encoder = JSONEncoder() 
if let data = try? encoder.encode(username) {
    namestore = data
}

let decoder = JSONDecoder()
if let name = try? decoder.decode(UserName.self, from: namestore) {
    username = name
}
@AppStorage("myimage") var imagestore: Data = Data()
 
var image = UIImage(named: "profilephoto")

if let data = image!.pngData() {
    imagestore = data
}

if let decodedImage: UIImage = UIImage(data: imagestore) {
     image = decodedImage
}

时间间隔

func minutesBetween(_ start: Date, and end: Date) -> Int {
    let diff = Calendar.current.dateComponents([.minute], from: start, to: end)
    guard let minute = diff.minute else { return 0 }
    return abs(minute)
}

样式文本

func textWithHashtags(_ text: String, color: Color) -> Text {
    let words = text.split(separator: " ")
    var output: Text = Text("")

    for word in words {
        if word.hasPrefix("#") { // Pick out hash in words
            output = output + Text(" ") + Text(String(word))
                .foregroundColor(color) // Add custom styling here
        } else {
            output = output + Text(" ") + Text(String(word))
        }
    }
    return output
}

struct ContentView: View {
    var body: some View {
        textWithHashtags("Here's a quick and easy way to include #hashtag formatting in your #SwiftUI Text Views. Learn how in the #Tutorial here.", color: .red)
            .font(Font.custom("Avenir Next", size: 20))
            .bold()
            
    }
}

UUID

var id = UUID().uuidString

Spacer不占地儿

Spacer(minLength: 0)

Image 大小

.aspectRatio(contentMode: .fit)

Namespace的使用

@Namespace var animation // 外部声明
TabButton(title: tab, selected: $selected, animation: animation) // 传入

var animation: Namespace.ID // 内部定义
.matchedGeometryEffect(id: "image\(item.id)", in: animation) // 使用

ZStack的一种用法

.background(
    ZStack {
        if selected == title {
            Color.black
        }
    }
)

好玩的图形

struct CustomShape: Shape {
    func path(in rect: CGRect) -> Path {
        return Path { path in
            let pt1 = CGPoint(x: rect.width, y: 0)
            let pt2 = CGPoint(x: 0, y: rect.height - 100)
            let pt3 = CGPoint(x: 0, y: rect.height)
            let pt4 = CGPoint(x: rect.width, y: rect.height)
            path.move(to: pt4)
            path.addArc(tangent1End: pt1, tangent2End: pt2, radius: 20)
            path.addArc(tangent1End: pt2, tangent2End: pt3, radius: 20)
            path.addArc(tangent1End: pt3, tangent2End: pt4, radius: 20)
            path.addArc(tangent1End: pt4, tangent2End: pt1, radius: 20)
        }
    }
}

最后一个不加哦

if tabs.last != tab {
    Spacer(minLength: 0)
}

LazyVGrid

LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 20), count: 2), spacing: 25, content: {

withAnimation

withAnimation(.spring()) {

}

安全区铺色

.background(
    Color.white
        .ignoresSafeArea()
)

异形底色

.background(
    Color.orange
        .clipShape(CustomShape())
)

多个选项,勾选的实现方法,ZStack比overlay好用

ForEach(1...4, id: \.self) { index in
    ZStack {
        Color("p\(index + 1)")
            .clipShape(Circle())
            .frame(width: 45, height: 45)
            .onTapGesture {
                withAnimation(.spring()) {
                    selectedColor = Color("p\(index + 1)")
                }
            }
        if selectedColor == Color("p\(index + 1)") {
            Image(systemName: "checkmark")
                .foregroundColor(.black)
        }
    }
}

显示/隐藏,height的原始值设置nil即可

.frame(height: loadContent ? nil : 0)
.opacity(loadContent ? 1 : 0)

圆角矩形框

.background(
    RoundedRectangle(cornerRadius: 15)
        .stroke(Color.black, lineWidth: 1)
)

view出现时触发

.onAppear {
    withAnimation(Animation.spring().delay(0.45)) {
        loadContent.toggle()
    }
}

在某种条件下使用scrollview

ScrollView(UIScreen.main.bounds.height < 750 ? .vertical : .init(), content: {
})

动画技术

  • timingCurve
// If you need fine control over the animation curve’s shape, you can use the ==timingCurve(_:_:_:_)== type method. 
  • Creating spring animations
.animation(
  .interpolatingSpring(
    mass: 1,
    stiffness: 100,
    damping: 10,
    initialVelocity: 0
    ) 
)
/* 
The parameters you pass are the same mentioned above:
 • mass: Controls how long the system “bounces”.
 • stiffness: Controls the speed of the initial movement.
 • damping: Controls how fast the system slows down and stops. 
 • initialVelocity: Gives an extra initial motion.
 */
 ```
 
 ```swift
.animation(
  .spring(
    response: 0.55,
    dampingFraction: 0.45,
    blendDuration: 0
) )
  • 延时
.animation(Animation.easeInOut.delay(Double(history.day) * 0.1))
  • 封装Animation
func barAnimation(_ barNumber: Int) -> Animation {
  return Animation.easeInOut.delay(Double(barNumber) * 0.1)
}

.animation(barAnimation(history.day))
var walkingAnimation: Animation {
  Animation
    .linear(duration: 3.0)
    .repeatForever(autoreverses: false)
}

.animation(walkingAnimation)
  • Transition
Group {
  if showTerminal {
    Text("Hide Terminal Map")
  } else {
    Text("Show Terminal Map")
  }
}
.transition(.slide)

.transition(.move(edge: .bottom))

extension AnyTransition {
  static var buttonNameTransition: AnyTransition {
    AnyTransition.slide
  }
}

if showTerminal {
  FlightTerminalMap(flight: flight)
    .transition(.buttonNameTransition)
}
  • async transiton
extension AnyTransition {
  static var buttonNameTransition: AnyTransition {
    let insertion = AnyTransition.move(edge: .trailing)
      .combined(with: .opacity)
    let removal = AnyTransition.scale(scale: 0.0)
      .combined(with: .opacity)
    return .asymmetric(insertion: insertion, removal: removal)
  }
}
  • Hero animation
.matchedGeometryEffect

异步任务的Button

struct AsyncButton<Content: View>: View {
    var isComplete: Bool
    let action: ()->()
    let content: Content
    
    @State private var inProgress: Bool
    
    init(isComplete: Bool, action: @escaping ()->(), @ViewBuilder label: ()->Content) {
        self.action = action
        self.isComplete = isComplete
        self.content = label()
        self._inProgress = State.init(initialValue: false)
    }
    
    var body: some View {
        Button(action: {
            if !inProgress { action() }
            withAnimation(Animation.easeInOut(duration: 0.4)) {
                inProgress = true
            }

        }, label: {
            VStack(alignment: .trailing) {
                if inProgress && !isComplete {
                    ProgressView()
                        .foregroundColor(.white)
                } else if isComplete {
                    Image(systemName: "checkmark")
                        .resizable()
                        .frame(width: 15, height: 15, alignment: .center)
                        .foregroundColor(.white)
                } else {
                    content
                }
            }
            .frame(maxWidth: isComplete || inProgress ? 50 : .infinity, maxHeight: isComplete  || inProgress ? 50 : nil, alignment: .center)
            .padding(.vertical, isComplete  || inProgress ? 0 : 12)
            .foregroundColor(.white)
            .background(Color.green)
            .cornerRadius(isComplete || inProgress ? 25 : 8)
            .font(Font.body.weight(.semibold))
            .padding(.all, 20)
        })
    }
}
    
struct ContentView: View {
    @State var complete: Bool = false
    @State var inProgress: Bool = false
    
    var body: some View {
        AsyncButton(isComplete: complete, action: {
            inProgress = true
            // Start Async Task (Download, Submit, etc)
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                withAnimation {
                    complete = true
                }
            }
        }) {
            Text(complete || inProgress ? "" : "Submit")
        }
    }
}

菜单/X按钮的动画实现

Button(action: {
    withAnimation(.spring()){
        showMenu.toggle()
    }
}, label: {
    VStack (spacing: 5){
        Capsule()
            .fill(showMenu ? Color.white : Color.primary)
            .frame(width: 30, height: 3)
            .rotationEffect(.init(degrees: showMenu ? -50 : 0))
            .offset(x: showMenu ? 2 : 0, y: showMenu ? 9 : 0)
        VStack (spacing:5) {
            Capsule()
                .fill(showMenu ? Color.white : Color.primary)
                .frame(width: 30, height: 3)
            Capsule()
                .fill(showMenu ? Color.white : Color.primary)
                .frame(width: 30, height: 3)
                .offset(y: showMenu ? -8 : 0)
        }
        .rotationEffect(.init(degrees: showMenu ? 50 : 0))
    }
})
.padding()

alignmentGuide的使用方法

VStack(alignment: .leading) {
    Rectangle()
        .foregroundColor(Color.green)
        .frame(width: 120, height: 50)
    Rectangle()
        .foregroundColor(Color.red)
        .alignmentGuide(.leading, computeValue: { d in 120.0 })
        .frame(width: 200, height: 50)
    Rectangle()
        .foregroundColor(Color.blue)
        .frame(width: 180, height: 50)
} 

// .alignmentGuide(.leading, computeValue: { d in d.width / 3 }) 
// .alignmentGuide(.leading, computeValue: { d in d[HorizontalAlignment.trailing] + 20})

AnimatableModifier 废弃

用Animatable, ViewModifier

struct NumberView: Animatable, ViewModifier {
    var number: Int
    
    var animatableData: CGFloat {
        get { CGFloat(number) }
        set { number = Int(newValue) }
    }
    
    func body(content: Content) -> some View {
        Text(String(number))
    }
}

生命周期

struct LifecycleDemoApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase, perform: { phase in
                switch phase {
                    case .active:
                        print("Active")
                    case .inactive:
                        print("Inactive")
                    case .background:
                        print("Background")
                    default:
                        print("Unknown scenephase")
                }
            })
    }
}

ContentShapeKinds有什么用?

参考

Image("leaf")
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(width: 300, height: 300)
    .clipShape(Circle())
    .contentShape(ContentShapeKinds.contextMenuPreview, Circle())
    .contextMenu {
        Text("Menu Item")
    }

contextMenu的用法

struct ContentView: View {
    @State private var foregroundColor: Color = Color.black
    @State private var backgroundColor: Color = Color.white
    
    var body: some View {
        Text("Hello, world!")
            .font(.largeTitle)
            .padding()
            .foregroundColor(foregroundColor)
            .background(backgroundColor)
            .contextMenu {
                Button(action: {
                    self.foregroundColor = .black
                    self.backgroundColor = .white
                }) {
                    Text("Normal Colors")
                    Image(systemName: "paintbrush")
                }
                 
                Button(action: {
                    self.foregroundColor = .white
                    self.backgroundColor = .black
                }) {
                    Text("Inverted Colors")
                    Image(systemName: "paintbrush.fill")
                }
            }
    }
}

@GestureState

  • 三个例子
  • 并使用了如下两种多手势组合使用的方法
    • 同时:.simultaneously(with: DragGesture())
    • 排队:.sequenced(before: DragGesture())
import SwiftUI

struct ContentView_testGestureState1: View {
    @GestureState private var offset: CGSize = .zero
    
    var body: some View {
        let drag = DragGesture()
            .updating($offset) { dragValue, state, transaction in
                state = dragValue.translation
            }
        
        return Image(systemName: "hand.point.right.fill")
            .font(.largeTitle)
            .offset(offset)
            .gesture(drag)
    }
}

struct ContentView_testGestureState2: View {
    @GestureState private var offset: CGSize = .zero
    @GestureState private var longPress: Bool = false
    
    var body: some View {
        let longPressAndDrag = LongPressGesture(minimumDuration: 1.0)
            .updating($longPress) { value, state, transition in
                state = value
            }
            .simultaneously(with: DragGesture())
            .updating($offset) { value, state, transaction in
                state = value.second?.translation ?? .zero
            }
        
        return Image(systemName: "hand.point.right.fill")
            .foregroundColor(longPress ? Color.red : Color.blue)
            .font(.largeTitle)
            .offset(offset)
            .gesture(longPressAndDrag)
    }
}

struct ContentView_testGestureState3: View {
    @GestureState private var offset: CGSize = .zero
    @State private var dragEnabled: Bool = false
    
    var body: some View {
        let longPressBeforeDrag = LongPressGesture(minimumDuration: 2.0)
            .onEnded( { _ in
                self.dragEnabled = true
            })
            .sequenced(before: DragGesture())
            .updating($offset) { value, state, transaction in
                switch value {
                case .first(true):
                    print("Long press in progress")
                case .second(true, let drag):
                    state = drag?.translation ?? .zero
                default: break
                }
            }
            .onEnded { value in
                self.dragEnabled = false
            }
        
        return Image(systemName: "hand.point.right.fill")
            .foregroundColor(dragEnabled ? Color.green : Color.blue)
            .font(.largeTitle)
            .offset(offset)
            .gesture(longPressBeforeDrag)
    }
}

DragGesture

struct ContentView: View {
    enum SwipeHorizontalDirection: String {
        case left, right, none
    }
    
    @State var swipeHorizontalDirection: SwipeHorizontalDirection = .none { didSet { print(swipeHorizontalDirection) } }
    
    var body: some View {
        VStack {
            Text(swipeHorizontalDirection.rawValue)
        }
        .gesture(
            DragGesture()
                .onChanged {
                    if $0.startLocation.x > $0.location.x {
                        self.swipeHorizontalDirection = .left
                    } else if $0.startLocation.x == $0.location.x {
                        self.swipeHorizontalDirection = .none
                    } else {
                        self.swipeHorizontalDirection = .right
                    }
                })
    }
}

Parallelogram Shape and Mask

struct Parallelogram: Shape {
    var depth: CGFloat
    var flipped: Bool = false
    
    func path(in rect: CGRect) -> Path {
        Path { p in
            if flipped {
                p.move(to: CGPoint(x: 0, y: 0))
                p.addLine(to: CGPoint(x: rect.width, y: depth))
                p.addLine(to: CGPoint(x: rect.width, y: rect.height))
                p.addLine(to: CGPoint(x: 0, y: rect.height - depth))
            } else {
                p.move(to: CGPoint(x: 0, y: depth))
                p.addLine(to: CGPoint(x: rect.width, y: 0))
                p.addLine(to: CGPoint(x: rect.width, y: rect.height - depth))
                p.addLine(to: CGPoint(x: 0, y: rect.height))
            }
            p.closeSubpath()
        }
    }
}

struct ContentView: View {
    
    var body: some View {
        Image("pic")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(height: 300, alignment: .center)
            .mask(Parallelogram(depth: 50, flipped: false))
    }
}

ScrollView Sticky Header

struct StickyHeader<Content: View>: View {
    var minHeight: CGFloat
    var content: Content
    
    init(minHeight: CGFloat = 200, @ViewBuilder content: () -> Content) {
        self.minHeight = minHeight
        self.content = content()
    }
    
    var body: some View {
        GeometryReader { geo in
            if(geo.frame(in: .global).minY <= 0) {
                content
                    .frame(width: geo.size.width, height: geo.size.height, alignment: .center)
            } else {
                content
                    .offset(y: -geo.frame(in: .global).minY)
                    .frame(width: geo.size.width, height: geo.size.height + geo.frame(in: .global).minY)
            }
        }.frame(minHeight: minHeight)
    }
}

struct ContentView: View {
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            StickyHeader {
                StickyHeader {
                    Image("pic")
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                }
            }
            
            // MARK: Scroll View Content Here
        }
    }
}

获取屏幕数据

extension View {
    func getRect() -> CGRect {
        return UIScreen.main.bounds
    }
    
    func getSageArea() -> UIEdgeInsets {
        return UIApplication.shared.windows.first?.safeAreaInsets ?? UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }
}

视图的init()和滚动视图的防回弹

init() { UIScrollView.appearance().bounces = false }

获得滚动视图的当前offset

.overlay(
    GeometryReader { proxy -> Color in
        DispatchQueue.main.async {
            if startOffset == 0 {
                self.startOffset = proxy.frame(in: .global).minY
            }
            
            let offset =  proxy.frame(in: .global).minY
            self.scrollViewOffset = offset - startOffset
        }
        return Color.clear
    }
    .frame(width: 0, height: 0, alignment: .top), alignment: .top)

双shadow

.shadow(color: Color.primary.opacity(0.1), radius: 5, x: 5, y: 5)
.shadow(color: Color.primary.opacity(0.1), radius: 5, x: -5, y: -5)

持续动画

func doAnimation(){
    withAnimation(Animation.easeInOut(duration:0.6).repeatForever(autoreverses: true)) {   
        animateBall.toggle()
    }
    withAnimation(Animation.easeInOut(duration:0.8).repeatForever(autoreverses: true)) {   
        animateRotation.toggle()
    }   
}

3D效果实现圆到椭圆

Circle()
    .fill(Color.gray.opacity(0.4))
    .frame(width: animateBall ?  40 : 20, height: 40)
    .rotation3DEffect(
        .init(degrees: 70),
        axis: (x: 1, y: 0, z: 0),
        anchor: .center
    )

3D效果

.rotation3DEffect(.init(degrees: getProgress() * 90), axis:(x:0, y: 1, z: 0), anchor: offset > 0 ?.leading : .trailing, anchorZ: 0, perspective: 0.5)

自定义圆角

struct CustomCorner: Shape {
    var corners: UIRectCorner
    var radius: CGFloat
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners,cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

用zIndex调整前后次序

.zIndex(1) // 后
.zIndex(0) // 前

@ViewBuilder

struct CustomStackView<Title: View, Content:View>: View {
    var titleView: Title
    var contentView: Content
    init(@ViewBuilder titleView:@escaping () ->Title, @ViewBuilder contentView:@escaping () ->Content) {
        self.contentView = contentView()
        self.titleView = titleView()
    }

控件颜色

.tint(.purple)

延迟动画

.onAppear {
     DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        withAnimation(.spring()) {

CustomCorner

struct CustomCorner: Shape {
    var corners: UIRectCorner
    var radius: CGFloat
    
    func  path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

struct ContentView: View {
    
    var body: some View {
        CustomCorner(corners: UIRectCorner.topLeft, radius: 30)
            .fill(Color.purple)
            .frame(width: 240, height: 80)
    }
}

获取屏幕大小 / 0圆角的作用

GeometryReader { proxy in
    let frame = proxy.frame(in: .global)
    Image(selectedTab.image)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(width: frame.width, height: frame.height, alignment: .center)
        //why we using corner radius
        //because image in SwiftUI using .fill
        //will require corner radius or clipshape to cut the image
        .cornerRadius(0)
}
.ignoresSafeArea()

当前值的判定

func getIndex() -> Int {
    let index = trips.firstIndex { (trip) -> Bool in
        return selectedTab.id == trip.id
    } ?? 0
    return index
}

多行文本居中

Text("Happy Birthday\nJustine")
    .kerning(3)
    .lineSpacing(10.0)
    .multilineTextAlignment(.center)

数组中随机元素

.randomElement()

禁止控件连击

Button(...
.disabled(wish) // wish 变量控制

Timer / Transition

struct CubeRotationModifier: AnimatableModifier {
    enum SlideDirection {
        case enter
        case exit
    }
    
    var pct: Double
    var direction: SlideDirection
    
    var animatableData: Double {
        get { pct }
        set { pct = newValue }
    }
      
    func body(content: Content) -> some View {
        GeometryReader { geo in
            content
                .rotation3DEffect(
                    Angle(degrees: calcRotation()),
                    axis: (x: 0.0, y: 1.0, z: 0.0),
                    anchor: direction == .enter ? .leading : .trailing,
                    anchorZ: 0,
                    perspective: 0.1
                ).transformEffect(.init(translationX: calcTranslation(geo: geo), y: 0))
        }
    }

    func calcTranslation(geo: GeometryProxy) -> CGFloat {
        if direction == .enter {
            return geo.size.width - (CGFloat(pct) * geo.size.width)
        } else {
            return -1 * (CGFloat(pct) * geo.size.width)
        }
    }

    func calcRotation() -> Double {
        if direction == .enter {
            return 90 - (pct * 90)
        } else {
            return -1 * (pct * 90)
        }
    }
}

extension AnyTransition {
    static var cubeRotation: AnyTransition {
        get {
            AnyTransition.asymmetric(
                insertion: AnyTransition.modifier(active: CubeRotationModifier(pct: 0, direction: .enter), identity: CubeRotationModifier(pct: 1, direction: .enter)),
                removal: AnyTransition.modifier(active: CubeRotationModifier(pct: 1, direction: .exit), identity: CubeRotationModifier(pct: 0, direction: .exit)))
        }
    }
}

struct ContentView: View {
    @State var index: Int = 0
    @State var timer = Timer.publish(every: 2, on: RunLoop.main, in: RunLoop.Mode.common).autoconnect()
    var colors: [Color] = [Color.red, Color.blue, Color.green, Color.orange, Color.purple]

    var body: some View {
        ZStack {
            ForEach(colors.indices) { i in
                if index == i {
                    colors[i]
                        .transition(.cubeRotation)
                }
            }
        }
        .frame(width: 200, height: 200, alignment: .center)
        .onReceive(timer) { _ in
            withAnimation(.easeInOut(duration: 1.3)) {
                index = (index + 1) % colors.count
            }
        }
    }
}

pinnedViews

ScrollView {
    LazyVStack(alignment: .center, spacing: 40, pinnedViews: [.sectionHeaders], content: {
        ForEach(0...50, id: \.self) { count in
            Section(header: stickyHeaderView) {
                Text("section \(count)")
            }
        }
    })
}

.init()

@StateObject var lockscreenModel: LockscreenModel = .init()

PhotosPicker

PhotosPicker(selection: $lockscreenModel.pickedItem, matching: .images, preferredItemEncoding: .automatic, photoLibrary: .shared()) {
    VStack(spacing: 10){
        Image(systemName: "plus.viewfinder")
            .font(.largeTitle)
        
        Text("Add Image")
    }
    .foregroundColor(.primary)
}

PreferenceKey

Color.clear
    .preference(key: RectKey.self, value: rect)
    .onPreferenceChange(RectKey.self) { value in
        lockscreenModel.textRect = value
    }
    
//
struct RectKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
}

Date To String Conversion

enum DateFormat: String {
    case hour = "hh"
    case minute = "mm"
    case seconds = "ss"
}

extension Date {
    func converToString(_ format: DateFormat) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = format.rawValue
        
        return formatter.string(from: self)
    }
}

像素点的Color值

extension UIView {
    func color(at point: CGPoint) -> (CGFloat, CGFloat, CGFloat, CGFloat) {
        let colorspace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
        
        var pixelData: [UInt8] = [0,0,0,0]
        
        let context = CGContext(data: &pixelData, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorspace, bitmapInfo: bitmapInfo.rawValue)
        context!.translateBy(x: -point.x, y: -point.y)
        self.layer.render(in: context!)
        let red = CGFloat(pixelData[0]) / 255
        let blue = CGFloat(pixelData[1]) / 255
        let green = CGFloat(pixelData[2]) / 255
        let alpha = CGFloat(pixelData[3]) / 255
        return (red, blue, green, alpha)
    }
}

screenSize

extension UIApplication {
    func screenSize()->CGSize{
        guard let window = connectedScenes.first as? UIWindowScene else { return .zero }
        return window.screen.bounds.size
    }
}

func makeUIView(context: Context) -> UIView {
    let size = UIApplication.shared.screenSize()
跟风买了一个滑行垫别人写的一篇博文