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