Last active
December 23, 2023 12:33
-
-
Save DarrenHurst/87519b70a4adabf7d487791bccc7761c to your computer and use it in GitHub Desktop.
Fun Holiday Tree Sample Swift UI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Gist one file for easy run | |
// snow.sks & fireflies.sks dependency | |
import Foundation | |
import SwiftUI | |
import SpriteKit | |
class Ornament: ObservableObject { | |
@Published var ornamentIndex: Int? | |
@Published var gif: (any View)? | |
} | |
@available(iOS 16.0, *) | |
struct Tree: View { | |
@State var startAnimation: Bool = false | |
@ObservedObject var model: Ornament | |
var snowScene: SKScene { | |
let scene = SnowScene() | |
scene.scaleMode = .resizeFill | |
scene.backgroundColor = .clear | |
return scene | |
} | |
var snowScene2: SKScene { | |
let scene = FireFlyScene() | |
scene.scaleMode = .resizeFill | |
scene.backgroundColor = .clear | |
return scene | |
} | |
var images: [String] = [ | |
"https://media.tenor.com/gF7eMTWxupAAAAAi/christmas-holiday.gif", | |
"https://media.tenor.com/397DwhVvgK0AAAAi/happy-dance.gif" | |
] | |
var body: some View { | |
VStack { | |
Text("Have a safe and joyful holiday season!") | |
buildTree().anyView | |
}.frame(idealWidth:300) | |
} | |
func letItSnow(_ r: GeometryProxy) -> some View { | |
return ZStack { | |
SpriteView(scene: snowScene, options: [.allowsTransparency]) | |
.ignoresSafeArea() | |
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).allowsHitTesting(false) | |
.frame(height:r.size.height) | |
SpriteView(scene: snowScene2, options: [.allowsTransparency]) | |
.ignoresSafeArea() | |
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).allowsHitTesting(false).frame(height:r.size.height) | |
}.offset(y:80).frame(height:r.size.height) | |
} | |
func buildTree() -> any View { | |
VStack { | |
GeometryReader { r in | |
drawTree(startAnimation: startAnimation, star: true).anyView.offset(x:-10, y:440) | |
drawTree(startAnimation: startAnimation, star: true).anyView.offset(x:110, y:290) | |
drawTree(startAnimation: startAnimation, star: true).anyView.offset(x:230, y:140) | |
drawOrnaments().anyView.offset(x:-20, y:50) | |
letItSnow(r) | |
}.offset(y:-90).opacity(0.9).anyView | |
}.onAppear() { | |
startAnimation = true | |
model.ornamentIndex = 1 | |
model.gif = gifImage(images[model.ornamentIndex ?? 1 ]) | |
} | |
.background(Color.black.opacity(0.3)) | |
.background(Color.blue) | |
} | |
func drawOrnaments() -> any View { | |
ZStack { | |
getOrnament(size: 80, offsetX: 0, offsetY: 0 ) | |
.opacity(0.7).anyView | |
}.offset(x:15, y:650) | |
} | |
func drawTree(startAnimation: Bool, star: Bool?) -> any View { | |
ZStack { | |
if star ?? false { | |
Image(systemName: "star.fill").font(.XLarge).offset(y:-10) | |
.foregroundColor(Color.yellow.opacity(startAnimation ? 0.9 : 0.6)) //.animation(Animation.easeIn(duration: 0.9) .repeatForever(), value: startAnimation) | |
Image(systemName: "star").font(.XLarge).offset(y:-10) | |
.foregroundColor(Color.black.opacity(startAnimation ? 0.4 : 0.1)) //.animation(Animation.easeIn(duration: 0.9) .repeatForever(), value: startAnimation) | |
} | |
ForEach(1...85, id: \.self) { index in | |
Circle() | |
.stroke(startAnimation ? | |
LinearGradient(colors: [.clear, .clear, .white.opacity(0.4), .green], startPoint: .leading, endPoint: .trailing) : | |
LinearGradient(colors: [.clear, .clear, .clear, .white], startPoint: .leading, endPoint: .trailing), style: StrokeStyle(lineWidth: startAnimation ? 9 : 5)) | |
.animation(Animation.easeIn(duration: 3.0), value: startAnimation) | |
.opacity(0.3) | |
.transformEffect(.init(scaleX: 6, y: 2)) | |
.frame( height: CGFloat(index * 2) ) | |
.rotationEffect(.degrees(90)) | |
.offset(y: CGFloat(index * 3)) | |
} | |
} | |
} | |
func getOrnament(size: CGFloat, offsetX: CGFloat, offsetY: CGFloat) -> any View { | |
ZStack { | |
Circle() | |
.fill(startAnimation ? | |
RadialGradient(colors: [.gray.opacity(0.1),.white,.clear, .white], center: UnitPoint(x: 0, y:0), startRadius: 5.0, endRadius: 200.0) : | |
RadialGradient(colors: [.clear,.white,.clear, .gray.opacity(0.1)], center: UnitPoint(x: 0, y:0), startRadius: 5.0, endRadius: 100.0) | |
) | |
.background(model.gif?.anyView.padding(5)) | |
.frame(height:size) | |
.offset(x: offsetX, y:offsetY) | |
.padding() | |
Circle() | |
.stroke(.black.opacity(0.1), style: StrokeStyle(lineWidth: 4)) | |
.frame(height:size) | |
.offset(x: offsetX, y:offsetY) | |
} | |
} | |
} | |
class SnowScene: SKScene { | |
let snowEmitterNode = SKEmitterNode(fileNamed: "snow") | |
override func didMove(to view: SKView) { | |
guard let snowEmitterNode = snowEmitterNode else { return } | |
snowEmitterNode.particleSize = CGSize(width: 50, height: 50) | |
snowEmitterNode.particleLifetime = 4 | |
snowEmitterNode.particleLifetimeRange = 8 | |
addChild(snowEmitterNode) | |
} | |
override func didChangeSize(_ oldSize: CGSize) { | |
guard let snowEmitterNode = snowEmitterNode else { return } | |
snowEmitterNode.particlePosition = CGPoint(x: size.width/2, y: size.height) | |
snowEmitterNode.particlePositionRange = CGVector(dx: size.width, dy: size.height) | |
} | |
} | |
class FireFlyScene: SKScene { | |
let snowEmitterNode = SKEmitterNode(fileNamed: "fireflies") | |
override func didMove(to view: SKView) { | |
guard let snowEmitterNode = snowEmitterNode else { return } | |
snowEmitterNode.particleSize = CGSize(width: 30, height: 30) | |
snowEmitterNode.particleLifetime = 4 | |
snowEmitterNode.particleLifetimeRange = 8 | |
addChild(snowEmitterNode) | |
} | |
override func didChangeSize(_ oldSize: CGSize) { | |
guard let snowEmitterNode = snowEmitterNode else { return } | |
snowEmitterNode.particlePosition = CGPoint(x: size.width/2, y: size.height) | |
snowEmitterNode.particlePositionRange = CGVector(dx: size.width, dy: size.height) | |
} | |
} | |
import WebKit | |
struct gifImage: UIViewRepresentable { | |
let name: String | |
init(_ name: String){ | |
self.name = name | |
} | |
func makeUIView(context: Context) -> WKWebView { | |
let webView = WKWebView() | |
let myURL = URL(string:name) | |
let myRequest = URLRequest(url: myURL!) | |
webView.load(myRequest) | |
webView.isOpaque = false | |
return webView | |
} | |
func updateUIView(_ uiView: WKWebView, context: Context) { | |
uiView.reload() | |
} | |
} | |
extension View { | |
var anyView: AnyView { | |
AnyView(self) | |
} | |
} | |
@available(iOS 16.0, *) | |
struct TreePreview: PreviewProvider { | |
static var previews: some View { | |
Tree(model: Ornament()) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simulator.Screen.Recording.-.iPhone.14.Pro.-.2023-12-23.at.07.23.59.mp4
File-New-Resource to create a snow.sks and fireflies.sks