Skip to content

Instantly share code, notes, and snippets.

@DarrenHurst
Last active December 23, 2023 12:33
Show Gist options
  • Save DarrenHurst/87519b70a4adabf7d487791bccc7761c to your computer and use it in GitHub Desktop.
Save DarrenHurst/87519b70a4adabf7d487791bccc7761c to your computer and use it in GitHub Desktop.
Fun Holiday Tree Sample Swift UI
// 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())
}
}
@DarrenHurst
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment