版本记录
版本号 | 时间 |
---|---|
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的动画的实现,感兴趣的给个赞或者关注~~~