SwiftUI 动画背景视图根据大小变化调整宽度(方向)

问题描述 投票:0回答:0

我有一个动画背景视图,它是一个实例化类的结构(视图)。它代表星星在屏幕上从右到左动画和移动。 视图 (StarsView) 在创建时被赋予宽度。然而,在尺寸变化时(比如从纵向到横向),星星(Starfield)保持初始宽度。 如何在尺寸更改时以正确的宽度重新初始化 Starfield?

我的视图包含在一个特殊的响应式视图中,因此我可以访问有关设备和大小的各种属性:

/// Responsive UI Properties
struct UIProperties: Equatable {
    var isLandscape: Bool
    var isiPad: Bool
    var isSplit: Bool
    // if the app is reduced more than 1/3 in split mode on iPads
    var isMaxSplit: Bool
    var isAdoptable: Bool
    var size: CGSize
}

// MARK: - Responsive View
/// Custom Responsive View which will give useful properties for creating adaptive UI
struct ResponsiveView<Content: View>: View {
    var content: (UIProperties) -> Content
    
    var body: some View {
        GeometryReader { proxy in
            let size = proxy.size
            let isLandscape = size.width > size.height
            let isiPad = UIDevice.current.userInterfaceIdiom == .pad
            let isSplit = isSplitscreen()
            let isMaxSplit = isSplit && size.width < 400
            
            // iPad vertical orientation; hide SideBar completely
            // Horizontal showing SideBar for 0.75 fraction
            let isAdoptable = isiPad && (isLandscape ? !isMaxSplit : !isSplit)
            
            let properties = UIProperties(isLandscape: isLandscape, isiPad: isiPad, isSplit: isSplit, isMaxSplit: isMaxSplit, isAdoptable: isAdoptable, size: size)

            content(properties)
                .frame(width: size.width, height: size.height)
        }
    }
    
    init(@ViewBuilder content: @escaping (UIProperties) -> Content) {
        self.content = content
    }
    
    // MARK: - Simple way to determine if the app is in split mode
    private func isSplitscreen() -> Bool {
        guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return false }
        return screen.windows.first?.frame.size != screen.screen.bounds.size
    }
}

ContentView 和以 StarsView 为背景的视图:

struct ContentView: View {
(...)    
    var body: some View {
        // Responsive View for adaptable layout orientations and devices
        ResponsiveView { properties in
                  GamesView(dataController: dataController, properties: properties)
                }
                .accentColor(.white)
    }
}

struct GamesView: View {
    // MARK: - Properties & State
    @StateObject var gamesViewModel: GamesViewModel
    var uiProperties: UIProperties
    (...)    

    // MARK: - Body
    var body: some View {
            ZStack (alignment: .bottom){
                background
                    .ignoresSafeArea()
                
                StarsView(uiProperties.size.width)
                    .ignoresSafeArea()
                    .opacity(starOpacity)
                
                VStack {
                    ScrollView {
                        gamiqText
                        
                        if gamesViewModel.filteredGames().count == 0 {
                            Spacer()
                                .frame(height: 88)
                            
                            noGamesView
                        } else {
                            horizontalList
                        }
                    }
                }
                .edgesIgnoringSafeArea([.leading, .trailing])
                
                utilitiesBar
            }
            (...)
}

星星自己看:

struct StarsView: View {
    var width: CGFloat
    
    @State var starField: StarField
    @State var meteorShower = MeteorShower()
    
    var body: some View {
        TimelineView(.animation) { timeline in
            Canvas { context, size in
                let timeInterval = timeline.date.timeIntervalSince1970
                
                // Update Starfield
                starField.update(date: timeline.date)
                
                // Update meteors before blurring!
                meteorShower.update(date: timeline.date, size: size)
                
                //let rightColors = [.clear, Color(red: 0.8, green: 1, blue: 1), .white]
                let rightColors = [Color.clear, .yellow.opacity(0.5), .orange.opacity(0.6), .white]
                let leftColors = Array(rightColors.reversed())
                                
                // Add Blur to StarField
                context.addFilter(.blur(radius: 0.3))
                
                for (index, star) in starField.stars.enumerated() {
                    let path = Path(ellipseIn: CGRect(x: star.x, y: star.y, width: star.size, height: star.size))
                    
                    if star.flickerInterval == 0 {
                        //Flashing (smaller) star
                        var flashLevel = sin(Double(index) + timeInterval * 4) //Sin varies between +1 and -1
                        flashLevel = abs(flashLevel)
                        flashLevel /= 2
                        context.opacity = 0.5 + flashLevel //values will always be between 0.5 and 1
                    } else {
                        //Blooming (bigger) star
                        var flashLevel = sin(Double(index) + timeInterval) //Sin varies between +1 and -1
                        //flashLevel = -1 to 1
                        //if we multiply that with the flickerInterval, which is 3 to 20
                        //Then: flashlevel will be -3 to 3 on the low ends and up to -20 to 20 on the high end
                        //Then: take away flashLevel - 1 will get us
                        // (19) -39 to 1 for opacity on the hight end, and
                        // (2) -5 to 1 on the low end
                        //SO: long time not visible bloom, then shortly visible (1)
                        flashLevel *= star.flickerInterval
                        flashLevel -= star.flickerInterval - 1
                        
                        //If flashlevel > 0 will add blurred (bloom) circles around (behind) our star
                        if flashLevel > 0 {
                            var contextCopy = context
                            contextCopy.opacity = flashLevel
                            contextCopy.addFilter(.blur(radius: 3))
                            
                            contextCopy.fill(path, with: .color(white: 1))
                            contextCopy.fill(path, with: .color(white: 1))
                            contextCopy.fill(path, with: .color(white: 1))
                        }
                        
                        context.opacity = 1 //reset
                    }
                    
                    //color variations and actual stars drawing (paths)
                    if index.isMultiple(of: 5) {
                        context.fill(path, with: .color(.orange.opacity(0.55)))
                    } else if index.isMultiple(of: 7) {
                        context.fill(path, with: .color(.yellow.opacity(0.85)))
                    } else {
                        context.fill(path, with: .color(white: 1))
                    }
                }
            }
        }
        .ignoresSafeArea()
        .mask( //Fade out stars near the bottom
            LinearGradient(colors: [.white, .clear], startPoint: .top, endPoint: .bottom)
        )
    }
    
    init(_ width: CGFloat) {
        self.width = width
        _starField = State(wrappedValue: StarField(width))
    }
}

最后但同样重要的是,StarField 课程:

class StarField {
    var width: CGFloat
    var stars = [Star]()
    let leftEdge = -50.0
    let rightEdge: CGFloat
    var lastUpdate = Date.now
    
    init(_ width: CGFloat) {
        self.width = width
        let numberOfStars = width > 500 ? 400 : 200
        rightEdge = width + 50
        
        for _ in 1...numberOfStars {
            let x = Double.random(in: leftEdge...rightEdge)
            let y = Double.random(in: 0...600)
            let size = Double.random(in: 1...3)
            let star = Star(x: x, y: y, size: size)
            stars.append(star)
        }
    }
    
    func update(date: Date) {
        let delta = date.timeIntervalSince1970 - lastUpdate.timeIntervalSince1970
        
        for star in stars {
            star.x -= delta * 2
            
            if star.x < leftEdge {
                star.x = rightEdge
            }
            
            lastUpdate = date
        }
    }
}
uiProperties.size.width

正确更新变化(风景,肖像等)

欢迎任何轻推和提示!谢谢!

swiftui device-orientation ios16 swiftui-view
© www.soinside.com 2019 - 2024. All rights reserved.