版本記錄
版本號 | 時間 |
---|---|
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的動畫的實現,感興趣的給個贊或者關注~~~