您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页SwiftUI框架详细解析 (九) —— 基于SwiftUI的动

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

来源:二三娱乐

版本记录

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

前言

源码

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)
       .scale(scale: 0.2, anchor: .topTrailing))
       .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的动画的实现,感兴趣的给个赞或者关注~~~

Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务