美文网首页SwiftUI
SwiftUI框架详细解析 (九) —— 基于SwiftUI的动

SwiftUI框架详细解析 (九) —— 基于SwiftUI的动

作者: 刀客传奇 | 来源:发表于2019-12-11 11:21 被阅读0次

    版本记录

    版本号 时间
    V1.0 2019.12.11 星期三

    前言

    今天翻阅苹果的API文档,发现多了一个框架SwiftUI,这里我们就一起来看一下这个框架。感兴趣的看下面几篇文章。
    1. SwiftUI框架详细解析 (一) —— 基本概览(一)
    2. SwiftUI框架详细解析 (二) —— 基于SwiftUI的闪屏页的创建(一)
    3. SwiftUI框架详细解析 (三) —— 基于SwiftUI的闪屏页的创建(二)
    4. SwiftUI框架详细解析 (四) —— 使用SwiftUI进行苹果登录(一)
    5. SwiftUI框架详细解析 (五) —— 使用SwiftUI进行苹果登录(二)
    6. SwiftUI框架详细解析 (六) —— 基于SwiftUI的导航的实现(一)
    7. SwiftUI框架详细解析 (七) —— 基于SwiftUI的导航的实现(二)
    8. SwiftUI框架详细解析 (八) —— 基于SwiftUI的动画的实现(一)

    源码

    1. Swift

    首先看下工程组织结构

    接下来就是源码了

    1. AppDelegate.swift
    
    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
      // MARK: - UISceneSession Lifecycle
      
      func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration",
                                    sessionRole: connectingSceneSession.role)
      }
    }
    
    2. SceneDelegate.swift
    
    import UIKit
    import SwiftUI
    
    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      var window: UIWindow?
      
      func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
          let window = UIWindow(windowScene: windowScene)
          window.rootViewController = UIHostingController(rootView: ContentView())
          self.window = window
          window.makeKeyAndVisible()
        }
      }
    }
    
    3. ContentView.swift
    
    import SwiftUI
    
    extension AnyTransition {
      static var customTransition: AnyTransition {
        let insertion = AnyTransition.move(edge: .top)
          .combined(with: .scale(scale: 0.2, anchor: .topTrailing))
          .combined(with: .opacity)
        let removal = AnyTransition.move(edge: .top)
        return .asymmetric(insertion: insertion, removal: removal)
      }
    }
    
    struct ContentView: View {
      @State var showMoon: String? = nil
      let moonAnimation = Animation.default
    
      func toggleMoons(_ name: String) -> Bool {
        return name == showMoon
      }
    
      var body: some View {
        List(planets) { planet in
          self.makePlanetRow(planet: planet)
        }
      }
    
      func makePlanetRow(planet: Planet) -> some View {
        VStack {
          HStack {
            Image(planet.name)
              .resizable()
              .aspectRatio(1, contentMode: .fit)
              .frame(height: 60)
            Text(planet.name)
            Spacer()
            if planet.hasMoons {
              Button(action: {
                withAnimation(.easeInOut) {
                  self.showMoon = self.toggleMoons(planet.name) ? nil : planet.name
                }
              }) {
                Image(systemName: "moon.circle.fill")
                  .rotationEffect(.degrees(self.toggleMoons(planet.name) ? -50 : 0))
                  .animation(nil)
                  .scaleEffect(self.toggleMoons(planet.name) ? 2 : 1)
                  .animation(moonAnimation)
              }
            }
          }
          if self.toggleMoons(planet.name) {
            MoonList(planet: planet)
              .transition(.customTransition)
          }
        }
      }
    }
    
    #if DEBUG
    struct ContentView_Previews: PreviewProvider {
      static var previews: some View {
        ContentView()
      }
    }
    #endif
    
    4. MoonList.swift
    
    import SwiftUI
    
    struct MoonList: View {
      let planet: Planet
      @State private var showModal = false
      @State private var selectedMoon: Moon?
    
      var body: some View {
        VStack {
          SolarSystem(planet: planet)
            .frame(height: 160)
          ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
              ForEach(planet.moons) { moon in
                Button(action: {
                  self.showModal = true
                  self.selectedMoon = moon
                }) {
                  HStack {
                    Image(systemName: "moon")
                    Text(moon.name)
                  }.sheet(isPresented: self.$showModal) {
                    PlanetInfo(planet: self.planet, startingMoon: self.selectedMoon!)
                  }
                }
              }
            }
          }
        }
      }
    }
    
    #if DEBUG
    struct MoonList_Previews: PreviewProvider {
      static var previews: some View {
        MoonList(planet: planets[5])
          .frame(width: 320)
      }
    }
    #endif
    
    5. SolarSystem.swift
    
    import SwiftUI
    
    struct SolarSystem: View {
      var moons: [Moon] { planet.moons }
      let planet: Planet
      @State private var animationFlag = false
    
      var body: some View {
        GeometryReader { geometry in
          self.makeSystem(geometry)
        }
      }
    
      func moonPath(planetSize: CGFloat, radiusIncrement: CGFloat, index: CGFloat) -> some View {
        return Circle()
          .stroke(Color.gray)
          .frame(width: planetSize + radiusIncrement * index,
                 height: planetSize + radiusIncrement * index)
      }
    
      func moon(planetSize: CGFloat,
                moonSize: CGFloat,
                radiusIncrement: CGFloat,
                index: CGFloat) -> some View {
        return Circle()
          .fill(Color.orange)
          .frame(width: moonSize, height: moonSize)
      }
    
      func makeSystem(_ geometry: GeometryProxy) -> some View {
        let planetSize = geometry.size.height * 0.25
        let moonSize = geometry.size.height * 0.1
        let radiusIncrement = (geometry.size.height - planetSize - moonSize) / CGFloat(moons.count)
        let range = 1 ... moons.count
        return
          ZStack {
            Circle()
              .fill(planet.drawColor)
              .frame(width: planetSize, height: planetSize, alignment: .center)
    
            ForEach(range, id: \.self) { index in
              // orbit paths
              self.moonPath(planetSize: planetSize, radiusIncrement: radiusIncrement, index: CGFloat(index))
            }
            ForEach(range, id: \.self) { index in
              // individual "moon" circles
              self.moon(planetSize: planetSize, moonSize: moonSize, radiusIncrement: radiusIncrement, index: CGFloat(index))
                  .modifier(self.makeOrbitEffect(
                    diameter: planetSize + radiusIncrement * CGFloat(index)
                  ))
                  .animation(Animation
                    .linear(duration: Double.random(in: 10 ... 100))
                    .repeatForever(autoreverses: false)
                  )
            }
        }
        .onAppear {
          self.animationFlag.toggle()
        }
      }
    
      func animation(index: Double) -> Animation {
        return Animation.spring(response: 0.55, dampingFraction: 0.45, blendDuration: 0)
          .speed(2)
          .delay(0.075 * index)
      }
    
      func makeOrbitEffect(diameter: CGFloat) -> some GeometryEffect {
        return OrbitEffect(angle: self.animationFlag ? 2 * .pi : 0,
                           radius: diameter / 2.0)
      }
    }
    
    struct OrbitEffect: GeometryEffect {
      let initialAngle = CGFloat.random(in: 0 ..< 2 * .pi)
    
      var angle: CGFloat = 0
      let radius: CGFloat
    
      var animatableData: CGFloat {
        get { return angle }
        set { angle = newValue }
      }
    
      func effectValue(size: CGSize) -> ProjectionTransform {
        let pt = CGPoint(x: cos(angle + initialAngle) * radius,
                         y: sin(angle + initialAngle) * radius)
        let translation = CGAffineTransform(translationX: pt.x, y: pt.y)
        return ProjectionTransform(translation)
      }
    }
    
    #if DEBUG
    struct SolarSystem_Previews: PreviewProvider {
      static var previews: some View {
        SolarSystem(planet: planets[5])
          .frame(width: 320, height: 240)
      }
    }
    #endif
    
    6. MoonView.swift
    
    import SwiftUI
    
    struct MoonView: View {
      @State var angle: CGFloat = -CGFloat.pi / 2
      let size: CGFloat
      let radius: CGFloat
      let targetAngle: CGFloat
    
      init(angle: CGFloat, size: CGFloat, radius: CGFloat) {
        self.targetAngle = angle
        self.size = size
        self.radius = radius
        self.angle = angle
      }
    
      var body: some View {
        return Circle()
          .fill(Color.orange)
          .frame(width: size, height: size)
          .offset(x: radius * cos(angle),
                  y: radius * sin(angle))
          .onAppear {
            withAnimation {
              self.angle = self.targetAngle
            }
          }
      }
    }
    
    7. PlanetInfo.swift
    
    import SwiftUI
    
    struct PlanetInfo: View {
      @Environment(\.presentationMode) var presentation
      let planet: Planet
      let startingMoon: Moon
    
      var numberFormatter: NumberFormatter = {
        let nf = NumberFormatter()
        nf.numberStyle = .decimal
        nf.usesGroupingSeparator = true
        nf.maximumFractionDigits = 0
        return nf
      }()
    
      var bigNumberFormatter: NumberFormatter = {
        let nf = NumberFormatter()
        nf.numberStyle = .scientific
        nf.usesGroupingSeparator = true
        nf.maximumFractionDigits = 0
        return nf
      }()
    
      var body: some View {
        VStack(alignment: .leading) {
          HStack {
            Text(planet.name)
              .font(.largeTitle)
            Spacer()
            Button("Done") {
              self.presentation.wrappedValue.dismiss()
            }
          }.padding()
          MoonFlow(selectedMoon: startingMoon, moons: planet.moons)
            .frame(height:200)
          Text("Radius: \(numberFormatter.string(for: planet.radius)!)km").padding()
          Text("Weight: \(bigNumberFormatter.string(for: planet.weight)!)kg").padding()
          Text("Gravity: \(planet.gravity)g").padding()
          Spacer()
        }
      }
    }
    
    8. MoonFlow.swift
    
    import SwiftUI
    
    struct MoonFlow: View {
      @State var selectedMoon: Moon
      var moons: [Moon]
    
      var body: some View {
        GeometryReader { geometry in
          ScrollView(.horizontal) {
            HStack(spacing: 20) {
              ForEach(self.moons) { moon in
                GeometryReader { moonGeometry in
                  VStack{
                    Image(uiImage: moon.image)
                      .resizable()
                      .frame(width:120, height: 120)
                    Text(moon.name)
                  }
                  .rotation3DEffect(
                    .degrees(Double(moonGeometry.frame(in: .global).midX - geometry.size.width / 2) / 3),
                    axis: (x: 0, y: 1, z: 0)
                  )
                }.frame(width:120)
              }
            }
            .frame(minWidth: geometry.size.width)
          }
          .frame(width: geometry.size.width)
        }
      }
    }
    
    #if DEBUG
    struct MoonFlow_Previews: PreviewProvider {
      static var previews: some View {
        MoonFlow(selectedMoon: planets[5].moons[0], moons: planets[5].moons)
      }
    }
    #endif
    
    9. PlanetModel.swift
    
    import SwiftUI
    
    let planets = [
      Planet(name: "Mercury",
             moons: [],
             radius: 2_439.7,
             weight: 3.3011e23,
             gravity: 0.38,
             drawColor: .gray),
      Planet(name: "Venus",
             moons: [],
             radius: 6_051.8,
             weight: 4.8675e24,
             gravity: 0.904,
             drawColor: .yellow),
      Planet(name: "Earth",
             moons: [Moon(name: "Luna")],
             radius: 6_371,
             weight: 5.97237e24,
             gravity: 1,
             drawColor: .blue),
      Planet(name: "Mars",
             moons: [Moon(name: "Phobos"),
                     Moon(name: "Deimos")],
             radius: 3_389.5,
             weight: 6.4171e23,
             gravity: 0.3794,
             drawColor: .red),
      Planet(name: "Jupiter",
             moons: [Moon(name: "Ganymede"),
                     Moon(name: "Callisto"),
                     Moon(name: "Europa"),
                     Moon(name: "Amalthea"),
                     Moon(name: "Himalia"),
                     Moon(name: "Thebe"),
                     Moon(name: "Elara")],
             radius: 69_911,
             weight: 1.8982e27,
             gravity: 2.528,
             drawColor: .orange),
      Planet(name: "Saturn",
             moons: [Moon(name: "Titan"),
                     Moon(name: "Rhea"),
                     Moon(name: "Iapetus"),
                     Moon(name: "Dione"),
                     Moon(name: "Tethys"),
                     Moon(name: "Enceladus"),
                     Moon(name: "Mimas"),
                     Moon(name: "Hyperion"),
                     Moon(name: "Phoebe"),
                     Moon(name: "Janus")],
             radius: 60_268,
             weight: 5.6834e26,
             gravity: 1.065,
             drawColor: .yellow),
      Planet(name: "Uranus",
             moons: [Moon(name: "Titania"),
                     Moon(name: "Oberon"),
                     Moon(name: "Umbriel"),
                     Moon(name: "Ariel"),
                     Moon(name: "Miranda")],
             radius: 25_362,
             weight: 8.6810e25,
             gravity: 0.886,
             drawColor: .blue),
      Planet(name: "Neptune",
             moons: [Moon(name: "Triton"),
                     Moon(name: "Proteus"),
                     Moon(name: "Nereid"),
                     Moon(name: "Larissa"),
                     Moon(name: "Galatea")],
             radius: 24_622,
             weight: 1.02413e26,
             gravity: 1.14,
             drawColor: .blue)
    ]
    
    struct Planet {
      let name: String
      let moons: [Moon]
      let radius: Double
      let weight: Double
      let gravity: Double
      let drawColor: Color
    
      var hasMoons: Bool { !moons.isEmpty }
    }
    
    extension Planet: Identifiable {
      var id: String {
        return name
      }
    }
    
    struct Moon {
      let name: String
    
      var image: UIImage {
        let path = Bundle.main.path(forResource: "\(name)".lowercased(), ofType: "jpg")
        if let path = path, let image = UIImage(contentsOfFile: path) {
          return image
        } else {
          return UIImage(contentsOfFile: Bundle.main.path(forResource: "titan".lowercased(), ofType: "jpg")!)!
        }
      }
    }
    
    extension Moon: Identifiable {
      var id: String {
        return name
      }
    }
    
    extension Moon: Equatable {}
    

    后记

    本篇主要讲述了基于SwiftUI的动画的实现,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:SwiftUI框架详细解析 (九) —— 基于SwiftUI的动

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