├── .DS_Store ├── GIF Previews ├── .DS_Store ├── .gitignore ├── SwiftUIChristmasTree.gif └── SwiftUIChristmasTree24.gif ├── README.md └── SwiftUIChristmasTree ├── .DS_Store ├── GIF Previews ├── .DS_Store ├── .gitignore ├── SwiftUIChristmasTree.gif └── SwiftUIChristmasTree24.gif ├── Packages └── RealityKitContent │ ├── .swiftpm │ └── xcode │ │ └── xcuserdata │ │ └── amosgyamfi.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── Package.realitycomposerpro │ ├── ProjectData │ │ └── main.json │ └── WorkspaceData │ │ ├── SceneMetadataList.json │ │ ├── Settings.rcprojectdata │ │ └── amosgyamfi.rcuserdata │ ├── Package.swift │ ├── README.md │ └── Sources │ └── RealityKitContent │ ├── RealityKitContent.rkassets │ ├── Materials │ │ └── GridMaterial.usda │ ├── ParticleEmitterPresetTextures │ │ ├── flare.exr │ │ ├── flaresheet.exr │ │ └── twinkle.exr │ └── Scene.usda │ └── RealityKitContent.swift ├── README.md ├── SwiftUIChristmasTree.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── amos.gyamfigetstream.io.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── amosgyamfi.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── amos.gyamfigetstream.io.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── amosgyamfi.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── SwiftUIChristmasTree ├── 2024-2025 ├── SwiftUINewYear2025.swift └── SwiftUIXmasTree24.swift ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── eyeLeft.imageset │ ├── Contents.json │ └── eyeLeft.svg ├── eyeRight.imageset │ ├── Contents.json │ └── eyeRight.svg ├── mouth.imageset │ ├── Contents.json │ └── mouth.svg ├── newyear2025darker.imageset │ ├── Contents.json │ └── newyear2025darker.png └── xmasSurprise.imageset │ ├── Contents.json │ └── xmasSurprise.svg ├── CompositorView.swift ├── FatherChristmasView.swift ├── GiftWithRibbonView.swift ├── HeartWithRibbonView.swift ├── MrsClausView.swift ├── MxClausView.swift ├── PresentView.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── SwiftUIChristmasTreeApp.swift ├── SwiftUIXmasTree.swift └── XmasSurpriseView.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/.DS_Store -------------------------------------------------------------------------------- /GIF Previews/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/GIF Previews/.DS_Store -------------------------------------------------------------------------------- /GIF Previews/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /GIF Previews/SwiftUIChristmasTree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/GIF Previews/SwiftUIChristmasTree.gif -------------------------------------------------------------------------------- /GIF Previews/SwiftUIChristmasTree24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/GIF Previews/SwiftUIChristmasTree24.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎄 The SwiftUI Christmas Tree 🌲 2 | 3 | ## Pure SwiftUI Christmas Tree and Fireworks Animations with yearly updates 4 | 5 | Explore the Xcode project folder and check out the various SwiftUI animation files. Enjoy. 6 | 7 | ![SwiftUI Christmas Tree](https://github.com/GetStream/SwiftUIChristmasTree/blob/main/GIF%20Previews/SwiftUIChristmasTree24.gif) 8 | 9 | ## 2024 Christmas Tree 10 | 11 | ```swift 12 | import SwiftUI 13 | 14 | struct SwiftUIXmasTree24: View { 15 | @State private var isSpinning = false 16 | 17 | // MARK: - Constants 18 | private enum Constants { 19 | static let animationDuration = 1.5 20 | static let rotation3D = 60.0 21 | static let ornamentCount = 4 22 | static let spinAngle = 310.0 23 | static let staticAngle = 50.0 24 | } 25 | 26 | // MARK: - White Shades 27 | private let whiteShades = (1...10).map { Color.white.opacity(1.1 - Double($0) * 0.1) } 28 | 29 | // MARK: - Circle Configurations 30 | private struct CircleConfig { 31 | let size: CGFloat 32 | let strokeWidth: CGFloat 33 | let yOffset: CGFloat 34 | let ornament: String 35 | let ornamentSize: CGFloat 36 | let ornamentOffset: CGFloat 37 | } 38 | 39 | private let circleConfigs: [CircleConfig] = [ 40 | CircleConfig(size: 20, strokeWidth: 1, yOffset: -160, ornament: "✨", ornamentSize: 4, ornamentOffset: -10), 41 | CircleConfig(size: 50, strokeWidth: 2, yOffset: -120, ornament: "🌟", ornamentSize: 6, ornamentOffset: -25), 42 | CircleConfig(size: 80, strokeWidth: 3, yOffset: -80, ornament: "★", ornamentSize: 8, ornamentOffset: -40), 43 | CircleConfig(size: 110, strokeWidth: 3, yOffset: -40, ornament: "⭐️", ornamentSize: 8, ornamentOffset: -55), 44 | CircleConfig(size: 140, strokeWidth: 3, yOffset: 0, ornament: "✨", ornamentSize: 10, ornamentOffset: -70), 45 | CircleConfig(size: 170, strokeWidth: 3, yOffset: 40, ornament: "✰", ornamentSize: 8, ornamentOffset: -85), 46 | CircleConfig(size: 200, strokeWidth: 5, yOffset: 80, ornament: "✧", ornamentSize: 10, ornamentOffset: -100), 47 | CircleConfig(size: 230, strokeWidth: 4, yOffset: 120, ornament: "♢", ornamentSize: 10, ornamentOffset: -115), 48 | CircleConfig(size: 260, strokeWidth: 5, yOffset: 160, ornament: "⭐︎", ornamentSize: 12, ornamentOffset: -130), 49 | CircleConfig(size: 290, strokeWidth: 5, yOffset: 200, ornament: "✦", ornamentSize: 12, ornamentOffset: -145) 50 | ] 51 | 52 | // MARK: - Helper Views 53 | private func CircleLayer(config: CircleConfig, index: Int) -> some View { 54 | ZStack { 55 | Circle() 56 | .stroke(lineWidth: config.strokeWidth) 57 | .frame(width: config.size, height: config.size) 58 | .foregroundStyle(whiteShades[index].gradient) 59 | 60 | ForEach(0.. 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RealityKitContent.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "pathsToIds" : { 3 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/GridMaterial.usda" : "440DE5B4-E4E4-459B-AABF-9ACE96319272", 4 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Scene.usda" : "0A9B4653-B11E-4D6A-850E-C6FCB621626C", 5 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Untitled Scene.usda" : "03E02005-EFA6-48D6-8A76-05B2822A74E9", 6 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/procedural_sphere_grid.usda" : "34C460AE-CA1B-4348-BD05-621ACBDFFE97", 7 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/GridMaterial.usda" : "FBD8436F-6B8B-4B82-99B5-995D538B4704", 8 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Scene.usda" : "26DBAE76-5DD8-47B6-A085-1B4ADA111097", 9 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/procedural_sphere_grid.usda" : "1CBF3893-ABFD-408C-8B91-045BFD257808" 10 | } 11 | } -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/SceneMetadataList.json: -------------------------------------------------------------------------------- 1 | { 2 | "03E02005-EFA6-48D6-8A76-05B2822A74E9" : { 3 | "objectMetadataList" : [ 4 | 5 | ] 6 | }, 7 | "0A9B4653-B11E-4D6A-850E-C6FCB621626C" : { 8 | "objectMetadataList" : [ 9 | [ 10 | "0A9B4653-B11E-4D6A-850E-C6FCB621626C", 11 | "Root", 12 | "GridMaterial" 13 | ], 14 | { 15 | "isExpanded" : true, 16 | "isLocked" : false 17 | }, 18 | [ 19 | "0A9B4653-B11E-4D6A-850E-C6FCB621626C", 20 | "Root" 21 | ], 22 | { 23 | "isExpanded" : true, 24 | "isLocked" : false 25 | }, 26 | [ 27 | "0A9B4653-B11E-4D6A-850E-C6FCB621626C", 28 | "Root", 29 | "Sphere" 30 | ], 31 | { 32 | "isExpanded" : true, 33 | "isLocked" : false 34 | } 35 | ] 36 | }, 37 | "1CBF3893-ABFD-408C-8B91-045BFD257808" : { 38 | "objectMetadataList" : [ 39 | 40 | ] 41 | }, 42 | "26DBAE76-5DD8-47B6-A085-1B4ADA111097" : { 43 | "objectMetadataList" : [ 44 | [ 45 | "26DBAE76-5DD8-47B6-A085-1B4ADA111097", 46 | "Root" 47 | ], 48 | { 49 | "isExpanded" : true, 50 | "isLocked" : false 51 | } 52 | ] 53 | }, 54 | "34C460AE-CA1B-4348-BD05-621ACBDFFE97" : { 55 | "objectMetadataList" : [ 56 | 57 | ] 58 | }, 59 | "440DE5B4-E4E4-459B-AABF-9ACE96319272" : { 60 | "objectMetadataList" : [ 61 | [ 62 | "440DE5B4-E4E4-459B-AABF-9ACE96319272", 63 | "Root" 64 | ], 65 | { 66 | "isExpanded" : true, 67 | "isLocked" : false 68 | } 69 | ] 70 | }, 71 | "FBD8436F-6B8B-4B82-99B5-995D538B4704" : { 72 | "objectMetadataList" : [ 73 | [ 74 | "FBD8436F-6B8B-4B82-99B5-995D538B4704", 75 | "Root" 76 | ], 77 | { 78 | "isExpanded" : true, 79 | "isLocked" : false 80 | } 81 | ] 82 | } 83 | } -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/Settings.rcprojectdata: -------------------------------------------------------------------------------- 1 | { 2 | "cameraPresets" : { 3 | 4 | }, 5 | "secondaryToolbarData" : { 6 | "isGridVisible" : true 7 | }, 8 | "unitDefaults" : { 9 | "kg" : "g", 10 | "kg⋅m²" : "kg⋅m²", 11 | "m" : "cm", 12 | "m\/s" : "m\/s", 13 | "m\/s²" : "m\/s²", 14 | "s" : "s", 15 | "°" : "°" 16 | } 17 | } -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/amosgyamfi.rcuserdata: -------------------------------------------------------------------------------- 1 | { 2 | "advancedEditorSelectedIdentifier" : "ProjectBrowser", 3 | "openSceneRelativePaths" : [ 4 | "Scene.usda" 5 | ], 6 | "recentIdentifiers" : { 7 | "26DBAE76-5DD8-47B6-A085-1B4ADA111097" : [ 8 | 9 | ] 10 | }, 11 | "sceneCameraHistory" : { 12 | 13 | }, 14 | "sceneViewportBackgroundColors" : { 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "RealityKitContent", 8 | platforms: [ 9 | .visionOS(.v2), 10 | .macOS(.v15), 11 | .iOS(.v18) 12 | ], 13 | products: [ 14 | // Products define the executables and libraries a package produces, and make them visible to other packages. 15 | .library( 16 | name: "RealityKitContent", 17 | targets: ["RealityKitContent"]), 18 | ], 19 | dependencies: [ 20 | // Dependencies declare other packages that this package depends on. 21 | // .package(url: /* package url */, from: "1.0.0"), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 26 | .target( 27 | name: "RealityKitContent", 28 | dependencies: []), 29 | ] 30 | ) -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/README.md: -------------------------------------------------------------------------------- 1 | # RealityKitContent 2 | 3 | A description of this package. -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Materials/GridMaterial.usda: -------------------------------------------------------------------------------- 1 | #usda 1.0 2 | ( 3 | defaultPrim = "Root" 4 | metersPerUnit = 1 5 | upAxis = "Y" 6 | ) 7 | 8 | def Xform "Root" 9 | { 10 | def Material "GridMaterial" 11 | { 12 | reorder nameChildren = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "DefaultSurfaceShader", "MaterialXPreviewSurface", "Texcoord", "Add", "Multiply", "Fractional", "LineCounts", "Multiply_1", "Separate2", "Separate2_1", "Ifgreater", "Ifgreater_1", "Max", "Background_Color"] 13 | token outputs:mtlx:surface.connect = 14 | token outputs:realitykit:vertex 15 | token outputs:surface 16 | float2 ui:nodegraph:realitykit:subgraphOutputs:pos = (2222, 300.5) 17 | float2 ui:nodegraph:realitykit:subgraphOutputs:size = (182, 89) 18 | int ui:nodegraph:realitykit:subgraphOutputs:stackingOrder = 749 19 | 20 | def Shader "DefaultSurfaceShader" 21 | { 22 | uniform token info:id = "UsdPreviewSurface" 23 | color3f inputs:diffuseColor = (1, 1, 1) 24 | float inputs:roughness = 0.75 25 | token outputs:surface 26 | } 27 | 28 | def Shader "MaterialXPreviewSurface" 29 | { 30 | uniform token info:id = "ND_UsdPreviewSurface_surfaceshader" 31 | float inputs:clearcoat 32 | float inputs:clearcoatRoughness 33 | color3f inputs:diffuseColor.connect = 34 | color3f inputs:emissiveColor 35 | float inputs:ior 36 | float inputs:metallic = 0.15 37 | float3 inputs:normal 38 | float inputs:occlusion 39 | float inputs:opacity 40 | float inputs:opacityThreshold 41 | float inputs:roughness = 0.5 42 | token outputs:out 43 | float2 ui:nodegraph:node:pos = (1967, 300.5) 44 | float2 ui:nodegraph:node:size = (208, 297) 45 | int ui:nodegraph:node:stackingOrder = 870 46 | string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["Advanced"] 47 | } 48 | 49 | def Shader "Texcoord" 50 | { 51 | uniform token info:id = "ND_texcoord_vector2" 52 | float2 outputs:out 53 | float2 ui:nodegraph:node:pos = (94.14453, 35.29297) 54 | float2 ui:nodegraph:node:size = (182, 43) 55 | int ui:nodegraph:node:stackingOrder = 1358 56 | } 57 | 58 | def Shader "Multiply" 59 | { 60 | uniform token info:id = "ND_multiply_vector2" 61 | float2 inputs:in1.connect = 62 | float2 inputs:in2 = (32, 15) 63 | float2 inputs:in2.connect = 64 | float2 outputs:out 65 | float2 ui:nodegraph:node:pos = (275.64453, 47.29297) 66 | float2 ui:nodegraph:node:size = (61, 36) 67 | int ui:nodegraph:node:stackingOrder = 1348 68 | string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:in2"] 69 | } 70 | 71 | def Shader "Fractional" 72 | { 73 | uniform token info:id = "ND_realitykit_fractional_vector2" 74 | float2 inputs:in.connect = 75 | float2 outputs:out 76 | float2 ui:nodegraph:node:pos = (440.5, 49.5) 77 | float2 ui:nodegraph:node:size = (155, 99) 78 | int ui:nodegraph:node:stackingOrder = 1345 79 | } 80 | 81 | def Shader "BaseColor" 82 | { 83 | uniform token info:id = "ND_constant_color3" 84 | color3f inputs:value = (0.89737034, 0.89737034, 0.89737034) ( 85 | colorSpace = "Input - Texture - sRGB - sRGB" 86 | ) 87 | color3f inputs:value.connect = None 88 | color3f outputs:out 89 | float2 ui:nodegraph:node:pos = (1537.5977, 363.07812) 90 | float2 ui:nodegraph:node:size = (150, 43) 91 | int ui:nodegraph:node:stackingOrder = 1353 92 | } 93 | 94 | def Shader "LineColor" 95 | { 96 | uniform token info:id = "ND_constant_color3" 97 | color3f inputs:value = (0.55945957, 0.55945957, 0.55945957) ( 98 | colorSpace = "Input - Texture - sRGB - sRGB" 99 | ) 100 | color3f inputs:value.connect = None 101 | color3f outputs:out 102 | float2 ui:nodegraph:node:pos = (1536.9844, 287.86328) 103 | float2 ui:nodegraph:node:size = (146, 43) 104 | int ui:nodegraph:node:stackingOrder = 1355 105 | } 106 | 107 | def Shader "LineWidths" 108 | { 109 | uniform token info:id = "ND_combine2_vector2" 110 | float inputs:in1 = 0.1 111 | float inputs:in2 = 0.1 112 | float2 outputs:out 113 | float2 ui:nodegraph:node:pos = (443.64453, 233.79297) 114 | float2 ui:nodegraph:node:size = (151, 43) 115 | int ui:nodegraph:node:stackingOrder = 1361 116 | } 117 | 118 | def Shader "LineCounts" 119 | { 120 | uniform token info:id = "ND_combine2_vector2" 121 | float inputs:in1 = 24 122 | float inputs:in2 = 12 123 | float2 outputs:out 124 | float2 ui:nodegraph:node:pos = (94.14453, 138.29297) 125 | float2 ui:nodegraph:node:size = (153, 43) 126 | int ui:nodegraph:node:stackingOrder = 1359 127 | } 128 | 129 | def Shader "Remap" 130 | { 131 | uniform token info:id = "ND_remap_color3" 132 | color3f inputs:in.connect = 133 | color3f inputs:inhigh.connect = None 134 | color3f inputs:inlow.connect = None 135 | color3f inputs:outhigh.connect = 136 | color3f inputs:outlow.connect = 137 | color3f outputs:out 138 | float2 ui:nodegraph:node:pos = (1755.5, 300.5) 139 | float2 ui:nodegraph:node:size = (95, 171) 140 | int ui:nodegraph:node:stackingOrder = 1282 141 | string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:outlow"] 142 | } 143 | 144 | def Shader "Separate2" 145 | { 146 | uniform token info:id = "ND_separate2_vector2" 147 | float2 inputs:in.connect = 148 | float outputs:outx 149 | float outputs:outy 150 | float2 ui:nodegraph:node:pos = (1212.6445, 128.91797) 151 | float2 ui:nodegraph:node:size = (116, 117) 152 | int ui:nodegraph:node:stackingOrder = 1363 153 | } 154 | 155 | def Shader "Combine3" 156 | { 157 | uniform token info:id = "ND_combine3_color3" 158 | float inputs:in1.connect = 159 | float inputs:in2.connect = 160 | float inputs:in3.connect = 161 | color3f outputs:out 162 | float2 ui:nodegraph:node:pos = (1578.1445, 128.91797) 163 | float2 ui:nodegraph:node:size = (146, 54) 164 | int ui:nodegraph:node:stackingOrder = 1348 165 | } 166 | 167 | def Shader "Range" 168 | { 169 | uniform token info:id = "ND_range_vector2" 170 | bool inputs:doclamp = 1 171 | float2 inputs:gamma = (2, 2) 172 | float2 inputs:in.connect = 173 | float2 inputs:inhigh.connect = 174 | float2 inputs:inlow = (0.02, 0.02) 175 | float2 inputs:outhigh 176 | float2 inputs:outlow 177 | float2 outputs:out 178 | float2 ui:nodegraph:node:pos = (990.64453, 128.91797) 179 | float2 ui:nodegraph:node:size = (98, 207) 180 | int ui:nodegraph:node:stackingOrder = 1364 181 | } 182 | 183 | def Shader "Subtract" 184 | { 185 | uniform token info:id = "ND_subtract_vector2" 186 | float2 inputs:in1.connect = 187 | float2 inputs:in2.connect = 188 | float2 outputs:out 189 | float2 ui:nodegraph:node:pos = (612.64453, 87.04297) 190 | float2 ui:nodegraph:node:size = (63, 36) 191 | int ui:nodegraph:node:stackingOrder = 1348 192 | } 193 | 194 | def Shader "Absval" 195 | { 196 | uniform token info:id = "ND_absval_vector2" 197 | float2 inputs:in.connect = 198 | float2 outputs:out 199 | float2 ui:nodegraph:node:pos = (765.64453, 87.04297) 200 | float2 ui:nodegraph:node:size = (123, 43) 201 | int ui:nodegraph:node:stackingOrder = 1348 202 | } 203 | 204 | def Shader "Min" 205 | { 206 | uniform token info:id = "ND_min_float" 207 | float inputs:in1.connect = 208 | float inputs:in2.connect = 209 | float outputs:out 210 | float2 ui:nodegraph:node:pos = (1388.1445, 128.91797) 211 | float2 ui:nodegraph:node:size = (114, 36) 212 | int ui:nodegraph:node:stackingOrder = 1363 213 | } 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ParticleEmitterPresetTextures/flare.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ParticleEmitterPresetTextures/flare.exr -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ParticleEmitterPresetTextures/flaresheet.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ParticleEmitterPresetTextures/flaresheet.exr -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ParticleEmitterPresetTextures/twinkle.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/ParticleEmitterPresetTextures/twinkle.exr -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Scene.usda: -------------------------------------------------------------------------------- 1 | #usda 1.0 2 | ( 3 | defaultPrim = "Root" 4 | metersPerUnit = 1 5 | upAxis = "Y" 6 | ) 7 | 8 | def Xform "Root" 9 | { 10 | reorder nameChildren = ["GridMaterial", "Sphere"] 11 | rel material:binding = None ( 12 | bindMaterialAs = "weakerThanDescendants" 13 | ) 14 | 15 | def "GridMaterial" ( 16 | active = true 17 | prepend references = @Materials/GridMaterial.usda@ 18 | ) 19 | { 20 | float3 xformOp:scale = (1, 1, 1) 21 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 22 | } 23 | 24 | def Xform "ParticleEmitter" 25 | { 26 | def RealityKitComponent "VFXEmitter" 27 | { 28 | token info:id = "RealityKit.VFXEmitter" 29 | 30 | def RealityKitStruct "currentState" 31 | { 32 | token birthDirection = "Normal" 33 | token birthLocation = "Volume" 34 | int64 burstCount = 100 35 | float3 emissionDirection = (0, 1, 0) 36 | double emissionDuration = 1 37 | double emissionDurationVariation = 0 38 | token emitterShape = "Cone" 39 | double idleDuration = 0 40 | double idleDurationVariation = 0 41 | bool isLocal = 0 42 | bool isSpawningEnabled = 1 43 | bool loops = 1 44 | float particleSpeed = 1.4 45 | float particleSpeedVariation = 0.1 46 | float3 shapeSize = (0.05, 1, 0.05) 47 | bool spawnInheritParentColor = 1 48 | token spawnOccasion = "OnDeath" 49 | float spawnSpreadFactor = 0.20000008 50 | float spawnSpreadFactorVariation = 0.1 51 | float spawnVelocityFactor = 0.1 52 | double warmupDuration = 0 53 | 54 | def RealityKitStruct "mainEmitter" 55 | { 56 | float3 acceleration = (0, -0.1, 0) 57 | token animationRepeatMode = "Looping" 58 | token billboardMode = "Billboard" 59 | float birthRate = 1.2 60 | float birthRateVariation = 1 61 | token blendMode = "Additive" 62 | float colorEvolutionPower = 1 63 | int64 columnCount = 2 64 | float dampingFactor = 3.2 65 | float4 endColorA = (0.0013309671, 0.03458923, 1, 1) 66 | float4 endColorB = (0.11386989, 0.0064959256, 0.10626237, 1) 67 | float frameRate = 0 68 | float frameRateVariation = 0 69 | int64 initialFrame = 0 70 | int64 initialFrameVariation = 3 71 | bool isAnimated = 1 72 | bool isLightingEnabled = 0 73 | float noiseAnimationSpeed = 0 74 | float noiseScale = 1 75 | float noiseStrength = 0 76 | token opacityOverLife = "Constant" 77 | float particleAngle = 0 78 | float particleAngleVariation = 0 79 | float particleAngularVelocity = 0 80 | float particleAngularVelocityVariation = 0 81 | asset particleImage = @ParticleEmitterPresetTextures/flaresheet.exr@ 82 | double particleLifeSpan = 0.52 83 | double particleLifeSpanVariation = 0 84 | float particleMass = 1 85 | float particleMassVariation = 0 86 | float particleSize = 0.004 87 | float particleSizeVariation = 0 88 | float3 radialGravityCenter = (0, 12, 0) 89 | float radialGravityStrength = -1 90 | int64 rowCount = 2 91 | float sizeMultiplierAtEndOfLifespan = 1 92 | float sizeMultiplierAtEndOfLifespanPower = 1 93 | token sortOrder = "IncreasingDepth" 94 | float spreadingAngle = 0.1 95 | float4 startColorA = (1, 0.051991113, 1, 1) 96 | float4 startColorB = (0, 0.98059916, 1, 1) 97 | float stretchFactor = 0 98 | bool useEndColor = 0 99 | bool useEndColorRange = 0 100 | bool useStartColorRange = 1 101 | float3 vortexDirection = (0, 1, 0) 102 | float vortexStrength = 0 103 | } 104 | 105 | def RealityKitStruct "spawnedEmitter" 106 | { 107 | float3 acceleration = (0, -0.15, 0) 108 | token animationRepeatMode = "Looping" 109 | token billboardMode = "Billboard" 110 | float birthRate = 39000 111 | float birthRateVariation = 8000 112 | token blendMode = "Additive" 113 | float colorEvolutionPower = 1 114 | int64 columnCount = 2 115 | float dampingFactor = 4 116 | float4 endColorA = (0.0013309671, 0.03458923, 1, 1) 117 | float4 endColorB = (0.11386989, 0.0064959256, 0.10626237, 1) 118 | float frameRate = 12 119 | float frameRateVariation = 3 120 | int64 initialFrame = 0 121 | int64 initialFrameVariation = 3 122 | bool isAnimated = 1 123 | bool isLightingEnabled = 0 124 | float noiseAnimationSpeed = 0.2 125 | float noiseScale = 3 126 | float noiseStrength = 0.02 127 | token opacityOverLife = "LinearFadeOut" 128 | float particleAngle = 0 129 | float particleAngleVariation = 2 130 | float particleAngularVelocity = 0 131 | float particleAngularVelocityVariation = 0 132 | asset particleImage = @ParticleEmitterPresetTextures/flaresheet.exr@ 133 | double particleLifeSpan = 1.8 134 | double particleLifeSpanVariation = 0.5 135 | float particleMass = 0.75 136 | float particleMassVariation = 0.5 137 | float particleSize = 0.03 138 | float particleSizeVariation = 0.01 139 | float3 radialGravityCenter = (0, -3, -3) 140 | float radialGravityStrength = 0 141 | int64 rowCount = 2 142 | float sizeMultiplierAtEndOfLifespan = 0.01 143 | float sizeMultiplierAtEndOfLifespanPower = 0.03 144 | token sortOrder = "IncreasingID" 145 | float spreadingAngle = 3 146 | float4 startColorA = (1, 1, 1, 1) 147 | float4 startColorB = (1, 1, 1, 1) 148 | float stretchFactor = 0 149 | bool useEndColor = 0 150 | bool useEndColorRange = 0 151 | bool useStartColorRange = 0 152 | float3 vortexDirection = (0, 1, 0) 153 | float vortexStrength = 0 154 | } 155 | } 156 | } 157 | } 158 | 159 | def Xform "ParticleEmitter2" 160 | { 161 | def RealityKitComponent "VFXEmitter" 162 | { 163 | token info:id = "RealityKit.VFXEmitter" 164 | 165 | def RealityKitStruct "currentState" 166 | { 167 | token birthDirection = "Normal" 168 | token birthLocation = "Surface" 169 | int64 burstCount = 200 170 | float3 emissionDirection = (0, 1, 0) 171 | double emissionDuration = 1 172 | double emissionDurationVariation = 0 173 | token emitterShape = "Torus" 174 | double idleDuration = 0 175 | double idleDurationVariation = 0 176 | bool isLocal = 0 177 | bool isSpawningEnabled = 1 178 | bool loops = 1 179 | float particleSpeed = 1.4 180 | float particleSpeedVariation = 0.1 181 | float3 shapeSize = (0.08, 3, 0.08) 182 | bool spawnInheritParentColor = 1 183 | token spawnOccasion = "OnUpdate" 184 | float spawnSpreadFactor = 0.32217312 185 | float spawnSpreadFactorVariation = 0.1698132 186 | float spawnVelocityFactor = 0.4 187 | double warmupDuration = 1 188 | 189 | def RealityKitStruct "mainEmitter" 190 | { 191 | float3 acceleration = (0, -0.1, 0) 192 | token animationRepeatMode = "Looping" 193 | token billboardMode = "Billboard" 194 | float birthRate = 1.9 195 | float birthRateVariation = 1 196 | token blendMode = "Additive" 197 | float colorEvolutionPower = 1 198 | int64 columnCount = 6 199 | float dampingFactor = 3.2 200 | float4 endColorA = (0.0013309671, 0.03458923, 1, 1) 201 | float4 endColorB = (0.11386989, 0.0064959256, 0.10626237, 1) 202 | float frameRate = 0 203 | float frameRateVariation = 0 204 | int64 initialFrame = 4 205 | int64 initialFrameVariation = 3 206 | bool isAnimated = 1 207 | bool isLightingEnabled = 0 208 | float noiseAnimationSpeed = 0 209 | float noiseScale = 1 210 | float noiseStrength = 0 211 | token opacityOverLife = "Constant" 212 | float particleAngle = 0 213 | float particleAngleVariation = 0 214 | float particleAngularVelocity = 0 215 | float particleAngularVelocityVariation = 0 216 | asset particleImage = @ParticleEmitterPresetTextures/flaresheet.exr@ 217 | double particleLifeSpan = 0.82 218 | double particleLifeSpanVariation = 0 219 | float particleMass = 2 220 | float particleMassVariation = 0 221 | float particleSize = 0.008 222 | float particleSizeVariation = 0 223 | float3 radialGravityCenter = (0, 12, 0) 224 | float radialGravityStrength = -1 225 | int64 rowCount = 6 226 | float sizeMultiplierAtEndOfLifespan = 2 227 | float sizeMultiplierAtEndOfLifespanPower = 1 228 | token sortOrder = "IncreasingDepth" 229 | float spreadingAngle = 0.1 230 | float4 startColorA = (1, 0.051991113, 1, 1) 231 | float4 startColorB = (0, 0.98059916, 1, 1) 232 | float stretchFactor = 0 233 | bool useEndColor = 0 234 | bool useEndColorRange = 0 235 | bool useStartColorRange = 1 236 | float3 vortexDirection = (0, 1, 0) 237 | float vortexStrength = 0 238 | } 239 | 240 | def RealityKitStruct "spawnedEmitter" 241 | { 242 | float3 acceleration = (0, -0.15, 0) 243 | token animationRepeatMode = "Looping" 244 | token billboardMode = "Billboard" 245 | float birthRate = 39000 246 | float birthRateVariation = 8000 247 | token blendMode = "Additive" 248 | float colorEvolutionPower = 1 249 | int64 columnCount = 2 250 | float dampingFactor = 4 251 | float4 endColorA = (0.0013309671, 0.03458923, 1, 1) 252 | float4 endColorB = (0.11386989, 0.0064959256, 0.10626237, 1) 253 | float frameRate = 12 254 | float frameRateVariation = 3 255 | int64 initialFrame = 0 256 | int64 initialFrameVariation = 3 257 | bool isAnimated = 1 258 | bool isLightingEnabled = 0 259 | float noiseAnimationSpeed = 0.2 260 | float noiseScale = 3 261 | float noiseStrength = 0.02 262 | token opacityOverLife = "LinearFadeOut" 263 | float particleAngle = 0 264 | float particleAngleVariation = 2 265 | float particleAngularVelocity = 0 266 | float particleAngularVelocityVariation = 0 267 | asset particleImage = @ParticleEmitterPresetTextures/flaresheet.exr@ 268 | double particleLifeSpan = 1.8 269 | double particleLifeSpanVariation = 0.5 270 | float particleMass = 0.75 271 | float particleMassVariation = 0.5 272 | float particleSize = 0.03 273 | float particleSizeVariation = 0.01 274 | float3 radialGravityCenter = (0, -3, -3) 275 | float radialGravityStrength = 0 276 | int64 rowCount = 2 277 | float sizeMultiplierAtEndOfLifespan = 0.01 278 | float sizeMultiplierAtEndOfLifespanPower = 0.03 279 | token sortOrder = "IncreasingID" 280 | float spreadingAngle = 3 281 | float4 startColorA = (1, 1, 1, 1) 282 | float4 startColorB = (1, 1, 1, 1) 283 | float stretchFactor = 0 284 | bool useEndColor = 0 285 | bool useEndColorRange = 0 286 | bool useStartColorRange = 0 287 | float3 vortexDirection = (0, 1, 0) 288 | float vortexStrength = 0 289 | } 290 | } 291 | } 292 | } 293 | } 294 | 295 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Bundle for the RealityKitContent project 4 | public let realityKitContentBundle = Bundle.module 5 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/README.md: -------------------------------------------------------------------------------- 1 | # 🎄 The SwiftUI Christmas Tree 🌲 2 | 3 | ## Pure SwiftUI Christmas Tree and Fireworks Animations with yearly updates 4 | 5 | Explore the Xcode project folder and check out the various SwiftUI animation files. Enjoy. 6 | 7 | ![SwiftUI Christmas Tree](https://github.com/GetStream/SwiftUIChristmasTree/blob/main/GIF%20Previews/SwiftUIChristmasTree24.gif) 8 | 9 | ## 2024 Christmas Tree 10 | 11 | ```swift 12 | import SwiftUI 13 | 14 | struct SwiftUIXmasTree24: View { 15 | @State private var isSpinning = false 16 | 17 | // MARK: - Constants 18 | private enum Constants { 19 | static let animationDuration = 1.5 20 | static let rotation3D = 60.0 21 | static let ornamentCount = 4 22 | static let spinAngle = 310.0 23 | static let staticAngle = 50.0 24 | } 25 | 26 | // MARK: - White Shades 27 | private let whiteShades = (1...10).map { Color.white.opacity(1.1 - Double($0) * 0.1) } 28 | 29 | // MARK: - Circle Configurations 30 | private struct CircleConfig { 31 | let size: CGFloat 32 | let strokeWidth: CGFloat 33 | let yOffset: CGFloat 34 | let ornament: String 35 | let ornamentSize: CGFloat 36 | let ornamentOffset: CGFloat 37 | } 38 | 39 | private let circleConfigs: [CircleConfig] = [ 40 | CircleConfig(size: 20, strokeWidth: 1, yOffset: -160, ornament: "✨", ornamentSize: 4, ornamentOffset: -10), 41 | CircleConfig(size: 50, strokeWidth: 2, yOffset: -120, ornament: "🌟", ornamentSize: 6, ornamentOffset: -25), 42 | CircleConfig(size: 80, strokeWidth: 3, yOffset: -80, ornament: "★", ornamentSize: 8, ornamentOffset: -40), 43 | CircleConfig(size: 110, strokeWidth: 3, yOffset: -40, ornament: "⭐️", ornamentSize: 8, ornamentOffset: -55), 44 | CircleConfig(size: 140, strokeWidth: 3, yOffset: 0, ornament: "✨", ornamentSize: 10, ornamentOffset: -70), 45 | CircleConfig(size: 170, strokeWidth: 3, yOffset: 40, ornament: "✰", ornamentSize: 8, ornamentOffset: -85), 46 | CircleConfig(size: 200, strokeWidth: 5, yOffset: 80, ornament: "✧", ornamentSize: 10, ornamentOffset: -100), 47 | CircleConfig(size: 230, strokeWidth: 4, yOffset: 120, ornament: "♢", ornamentSize: 10, ornamentOffset: -115), 48 | CircleConfig(size: 260, strokeWidth: 5, yOffset: 160, ornament: "⭐︎", ornamentSize: 12, ornamentOffset: -130), 49 | CircleConfig(size: 290, strokeWidth: 5, yOffset: 200, ornament: "✦", ornamentSize: 12, ornamentOffset: -145) 50 | ] 51 | 52 | // MARK: - Helper Views 53 | private func CircleLayer(config: CircleConfig, index: Int) -> some View { 54 | ZStack { 55 | Circle() 56 | .stroke(lineWidth: config.strokeWidth) 57 | .frame(width: config.size, height: config.size) 58 | .foregroundStyle(whiteShades[index].gradient) 59 | 60 | ForEach(0.. 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/project.xcworkspace/xcuserdata/amos.gyamfigetstream.io.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/project.xcworkspace/xcuserdata/amos.gyamfigetstream.io.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/project.xcworkspace/xcuserdata/amosgyamfi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/project.xcworkspace/xcuserdata/amosgyamfi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/xcuserdata/amos.gyamfigetstream.io.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftUIChristmasTree.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree.xcodeproj/xcuserdata/amosgyamfi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftUIChristmasTree.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/2024-2025/SwiftUINewYear2025.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUINewYear2025.swift 3 | // SwiftUIChristmasTree 4 | // 5 | import SwiftUI 6 | 7 | struct SwiftUINewYear2025: View { 8 | @State private var isSpinning = false 9 | 10 | // MARK: - Constants 11 | private enum Constants { 12 | static let animationDuration = 1.5 13 | static let rotation3D = 60.0 14 | static let ornamentCount = 4 15 | static let spinAngle = 310.0 16 | static let staticAngle = 50.0 17 | } 18 | 19 | // MARK: - White Shades 20 | private let whiteShades = (1...10).map { Color.white.opacity(1.1 - Double($0) * 0.1) } 21 | 22 | // MARK: - Circle Configurations 23 | private struct CircleConfig { 24 | let size: CGFloat 25 | let strokeWidth: CGFloat 26 | let yOffset: CGFloat 27 | let ornament: String 28 | let ornamentSize: CGFloat 29 | let ornamentOffset: CGFloat 30 | } 31 | 32 | private let circleConfigs: [CircleConfig] = [ 33 | CircleConfig(size: 20, strokeWidth: 1, yOffset: -160, ornament: "✨", ornamentSize: 4, ornamentOffset: -10), 34 | CircleConfig(size: 50, strokeWidth: 2, yOffset: -120, ornament: "🌟", ornamentSize: 6, ornamentOffset: -25), 35 | CircleConfig(size: 80, strokeWidth: 3, yOffset: -80, ornament: "★", ornamentSize: 8, ornamentOffset: -40), 36 | CircleConfig(size: 110, strokeWidth: 3, yOffset: -40, ornament: "⭐️", ornamentSize: 8, ornamentOffset: -55), 37 | CircleConfig(size: 140, strokeWidth: 3, yOffset: 0, ornament: "✨", ornamentSize: 10, ornamentOffset: -70), 38 | CircleConfig(size: 170, strokeWidth: 3, yOffset: 40, ornament: "✰", ornamentSize: 8, ornamentOffset: -85), 39 | CircleConfig(size: 200, strokeWidth: 5, yOffset: 80, ornament: "✧", ornamentSize: 10, ornamentOffset: -100), 40 | CircleConfig(size: 230, strokeWidth: 4, yOffset: 120, ornament: "♢", ornamentSize: 10, ornamentOffset: -115), 41 | CircleConfig(size: 260, strokeWidth: 5, yOffset: 160, ornament: "⭐︎", ornamentSize: 12, ornamentOffset: -130), 42 | CircleConfig(size: 290, strokeWidth: 5, yOffset: 200, ornament: "✦", ornamentSize: 12, ornamentOffset: -145) 43 | ] 44 | 45 | // MARK: - Helper Views 46 | private func CircleLayer(config: CircleConfig, index: Int) -> some View { 47 | ZStack { 48 | Circle() 49 | .stroke(lineWidth: config.strokeWidth) 50 | .frame(width: config.size, height: config.size) 51 | .foregroundStyle(whiteShades[index].gradient) 52 | .opacity(0) 53 | 54 | ForEach(0.. some View { 43 | ZStack { 44 | Circle() 45 | .stroke(lineWidth: config.strokeWidth) 46 | .frame(width: config.size, height: config.size) 47 | .foregroundStyle(whiteShades[index].gradient) 48 | .opacity(0) 49 | 50 | ForEach(0.. 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/eyeRight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "eyeRight.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/eyeRight.imageset/eyeRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/mouth.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "mouth.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/mouth.imageset/mouth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/newyear2025darker.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "newyear2025darker.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/newyear2025darker.imageset/newyear2025darker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/SwiftUIChristmasTree/66f9def3108d3cca9cb482392d0931514f35bb6c/SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/newyear2025darker.imageset/newyear2025darker.png -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/xmasSurprise.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "xmasSurprise.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Assets.xcassets/xmasSurprise.imageset/xmasSurprise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/CompositorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositorView.swift 3 | // SwiftUIChristmasTree 4 | // 5 | // Created by amos @getstream.io on 23.12.2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CompositorView: View { 11 | var body: some View { 12 | VStack(spacing: -24) { 13 | XmasSurpriseView() 14 | .scaleEffect(0.12) 15 | .frame(width: 48, height: 64) 16 | .offset(x: -5) 17 | 18 | VStack{ 19 | SwiftUIXmasTree() 20 | 21 | Spacer() 22 | 23 | HStack{ 24 | HStack{ 25 | HeartWithRibbonView() 26 | .offset(y: -26) 27 | GiftWithRibbonView() 28 | .offset(y: -14) 29 | } 30 | .padding(.horizontal, 50) 31 | 32 | HStack{ 33 | MrsClausView() 34 | .offset(y: -14) 35 | FatherChristmasView() 36 | .offset(y: -26) 37 | MxClausView() 38 | .offset(y: -46) 39 | } 40 | .padding(.horizontal) 41 | } 42 | 43 | Spacer() 44 | } 45 | } 46 | } 47 | } 48 | 49 | #Preview { 50 | CompositorView() 51 | .preferredColorScheme(.dark) 52 | } 53 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/FatherChristmasView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FatherChristmasView.swift 3 | 4 | 5 | // MARK: Here the reaction icons scale up from the bottom and back to the original size 6 | 7 | import SwiftUI 8 | 9 | // Create a list of steps for the animation 10 | enum FatherChristmasScale: CaseIterable { 11 | // Define the phases of the animation 12 | // Initial appearance, move the view up and scale 13 | case initial, move, scale 14 | 15 | // Create a computed properties for the effects you want to have 16 | var verticalOffset: Double { 17 | switch self { 18 | case .initial: 0 19 | case .move, .scale: 0.0 20 | } 21 | } 22 | 23 | var scale: Double { 24 | switch self { 25 | case .initial: 1 26 | case .move, .scale: 2.0 27 | } 28 | } 29 | 30 | var chromaRotate: Double { 31 | switch self { 32 | case .initial: 0.0 33 | case .move, .scale: 225.0 34 | } 35 | } 36 | } 37 | 38 | struct FatherChristmasView: View { 39 | @State private var reactionCount = 0 40 | 41 | var body: some View { 42 | HStack { 43 | Text("🎅") 44 | .font(.largeTitle) 45 | .phaseAnimator( 46 | MrsClauseScale.allCases 47 | ) { content, phase in 48 | content 49 | .scaleEffect(phase.scale, anchor: .bottom) 50 | .offset(y: phase.verticalOffset) 51 | .hueRotation(.degrees(phase.chromaRotate)) 52 | } animation: { phase in 53 | switch phase { 54 | case .initial: .bouncy(duration: 1.5, extraBounce: 0.25) 55 | case .move: .easeInOut(duration: 0.6).delay(0.27) 56 | case .scale: .spring(duration: 1, bounce: 0.7) 57 | } 58 | } 59 | } 60 | 61 | } 62 | 63 | func nothing() {} 64 | } 65 | 66 | #Preview { 67 | FatherChristmasView() 68 | .preferredColorScheme(.dark) 69 | } 70 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/GiftWithRibbonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GiftWithRibbonView.swift 3 | 4 | import SwiftUI 5 | 6 | // Define the properties that will drive the animations. Create a new struct to contain all the properties that will be independently animated. 7 | struct ReactionAnimationProps { 8 | var scale = 1.0 9 | var verticalStretch = 1.0 10 | var verticalTranslation = 0.0 11 | var angle = Angle.zero 12 | var chromaAngle = Angle.zero 13 | } 14 | 15 | struct GiftWithRibbonView: View { 16 | 17 | var body: some View { 18 | 19 | Text("🎁") 20 | // Add the keyframe animator modifier 21 | .keyframeAnimator( 22 | // Provide an instance of the struct 23 | initialValue: ReactionAnimationProps()) { content, value in 24 | content 25 | // Apply modifiers on the view for each of the properties of the struct 26 | .rotationEffect(value.angle) 27 | .hueRotation(value.chromaAngle) 28 | .scaleEffect(value.scale) 29 | .scaleEffect(y: value.verticalStretch) 30 | .offset(y: value.verticalTranslation) 31 | 32 | } keyframes: { _ in 33 | // Provide the keyframe track for the different properties you are animating. You should specify them with a key path. 34 | KeyframeTrack(\.angle) { 35 | CubicKeyframe(.zero, duration: 0.58) 36 | CubicKeyframe(.degrees(16), duration: 0.125) 37 | CubicKeyframe(.degrees(-16), duration: 0.125) 38 | CubicKeyframe(.degrees(16), duration: 0.125) 39 | CubicKeyframe(.zero, duration: 0.125) 40 | } 41 | 42 | KeyframeTrack(\.verticalStretch) { 43 | CubicKeyframe(1.0, duration: 0.1) 44 | CubicKeyframe(0.6, duration: 0.15) 45 | CubicKeyframe(1.5, duration: 0.1) 46 | CubicKeyframe(1.05, duration: 0.15) 47 | CubicKeyframe(3.0, duration: 0.88) 48 | CubicKeyframe(0.8, duration: 0.1) 49 | CubicKeyframe(1.04, duration: 0.4) 50 | CubicKeyframe(1.0, duration: 0.22) 51 | } 52 | 53 | KeyframeTrack(\.scale) { 54 | // Specify the keyframe timing curve 55 | LinearKeyframe(1.0, duration: 0.36) 56 | SpringKeyframe(2.0, duration: 0.8, spring: .bouncy) 57 | SpringKeyframe(1, spring: .bouncy) 58 | } 59 | 60 | KeyframeTrack(\.verticalTranslation) { 61 | LinearKeyframe(0.0, duration: 0.1) 62 | SpringKeyframe(20.0, duration: 0.15, spring: .bouncy) 63 | SpringKeyframe(-240.0, duration: 1.0, spring: .bouncy) 64 | SpringKeyframe(0.0, spring: .bouncy) 65 | } 66 | 67 | KeyframeTrack(\.chromaAngle) { 68 | LinearKeyframe(.zero, duration: 0.58) 69 | LinearKeyframe(.degrees(45), duration: 0.125) 70 | LinearKeyframe(.degrees(-30), duration: 0.125) 71 | LinearKeyframe(.degrees(150), duration: 0.125) 72 | LinearKeyframe(.zero, duration: 0.125) 73 | } 74 | } 75 | } 76 | } 77 | 78 | #Preview { 79 | GiftWithRibbonView() 80 | .preferredColorScheme(.dark) 81 | } 82 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/HeartWithRibbonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GiftWithRibbonView.swift 3 | 4 | import SwiftUI 5 | 6 | // Define the properties that will drive the animations. Create a new struct to contain all the properties that will be independently animated. 7 | struct HeartRibbonAnimation { 8 | var scale = 1.0 9 | var verticalStretch = 1.0 10 | var verticalTranslation = 0.0 11 | var angle = Angle.zero 12 | var chromaAngle = Angle.zero 13 | } 14 | 15 | struct HeartWithRibbonView: View { 16 | 17 | var body: some View { 18 | 19 | Text("💝") 20 | // Add the keyframe animator modifier 21 | .keyframeAnimator( 22 | // Provide an instance of the struct 23 | initialValue: ReactionAnimationProps()) { content, value in 24 | content 25 | // Apply modifiers on the view for each of the properties of the struct 26 | .rotationEffect(value.angle) 27 | .hueRotation(value.chromaAngle) 28 | .scaleEffect(value.scale) 29 | .scaleEffect(y: value.verticalStretch) 30 | .offset(y: value.verticalTranslation) 31 | 32 | } keyframes: { _ in 33 | // Provide the keyframe track for the different properties you are animating. You should specify them with a key path. 34 | KeyframeTrack(\.angle) { 35 | CubicKeyframe(.zero, duration: 0.58) 36 | CubicKeyframe(.degrees(16), duration: 0.125) 37 | CubicKeyframe(.degrees(-16), duration: 0.125) 38 | CubicKeyframe(.degrees(16), duration: 0.125) 39 | CubicKeyframe(.zero, duration: 0.125) 40 | } 41 | 42 | KeyframeTrack(\.verticalStretch) { 43 | CubicKeyframe(1.0, duration: 0.1) 44 | CubicKeyframe(0.6, duration: 0.15) 45 | CubicKeyframe(1.5, duration: 0.1) 46 | CubicKeyframe(1.05, duration: 0.15) 47 | CubicKeyframe(1.0, duration: 0.88) 48 | CubicKeyframe(0.8, duration: 0.1) 49 | CubicKeyframe(1.04, duration: 0.4) 50 | CubicKeyframe(1.0, duration: 0.22) 51 | } 52 | 53 | KeyframeTrack(\.scale) { 54 | // Specify the keyframe timing curve 55 | LinearKeyframe(1.0, duration: 0.36) 56 | SpringKeyframe(2.0, duration: 0.8, spring: .bouncy) 57 | SpringKeyframe(1, spring: .bouncy) 58 | } 59 | 60 | KeyframeTrack(\.verticalTranslation) { 61 | LinearKeyframe(0.0, duration: 0.1) 62 | SpringKeyframe(20.0, duration: 0.15, spring: .bouncy) 63 | SpringKeyframe(-120.0, duration: 1.0, spring: .bouncy) 64 | SpringKeyframe(0.0, spring: .bouncy) 65 | } 66 | 67 | KeyframeTrack(\.chromaAngle) { 68 | LinearKeyframe(.zero, duration: 0.58) 69 | LinearKeyframe(.degrees(45), duration: 0.125) 70 | LinearKeyframe(.degrees(-30), duration: 0.125) 71 | LinearKeyframe(.degrees(150), duration: 0.125) 72 | LinearKeyframe(.zero, duration: 0.125) 73 | } 74 | } 75 | } 76 | } 77 | 78 | #Preview { 79 | HeartWithRibbonView() 80 | .preferredColorScheme(.dark) 81 | } 82 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/MrsClausView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MrsClausView.swift 3 | 4 | 5 | // MARK: Here the reaction icons scale up from the bottom and back to the original size 6 | 7 | import SwiftUI 8 | 9 | // Create a list of steps for the animation 10 | enum MrsClauseScale: CaseIterable { 11 | // Define the phases of the animation 12 | // Initial appearance, move the view up and scale 13 | case initial, move, scale 14 | 15 | // Create a computed properties for the effects you want to have 16 | var verticalOffset: Double { 17 | switch self { 18 | case .initial: 0 19 | case .move, .scale: 0.0 20 | } 21 | } 22 | 23 | var scale: Double { 24 | switch self { 25 | case .initial: 1 26 | case .move, .scale: 2.0 27 | } 28 | } 29 | 30 | var chromaRotate: Double { 31 | switch self { 32 | case .initial: 0.0 33 | case .move, .scale: 225.0 34 | } 35 | } 36 | } 37 | 38 | struct MrsClausView: View { 39 | @State private var reactionCount = 0 40 | 41 | var body: some View { 42 | HStack { 43 | Text("🤶") 44 | .font(.largeTitle) 45 | .phaseAnimator( 46 | MrsClauseScale.allCases 47 | ) { content, phase in 48 | content 49 | .scaleEffect(phase.scale, anchor: .bottom) 50 | .offset(y: phase.verticalOffset) 51 | .hueRotation(.degrees(phase.chromaRotate)) 52 | } animation: { phase in 53 | switch phase { 54 | case .initial: .bouncy(duration: 1.5, extraBounce: 0.25) 55 | case .move: .easeInOut(duration: 0.6).delay(0.25) 56 | case .scale: .spring(duration: 1, bounce: 0.7) 57 | } 58 | } 59 | } 60 | 61 | } 62 | 63 | func nothing() {} 64 | } 65 | 66 | #Preview { 67 | MrsClausView() 68 | .preferredColorScheme(.dark) 69 | } 70 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/MxClausView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MxClausView.swift 3 | 4 | 5 | // MARK: Here the reaction icons scale up from the bottom and back to the original size 6 | 7 | import SwiftUI 8 | 9 | // Create a list of steps for the animation 10 | enum MxClausScale: CaseIterable { 11 | // Define the phases of the animation 12 | // Initial appearance, move the view up and scale 13 | case initial, move, scale 14 | 15 | // Create a computed properties for the effects you want to have 16 | var verticalOffset: Double { 17 | switch self { 18 | case .initial: 0 19 | case .move, .scale: 0.0 20 | } 21 | } 22 | 23 | var scale: Double { 24 | switch self { 25 | case .initial: 1 26 | case .move, .scale: 2.0 27 | } 28 | } 29 | 30 | var chromaRotate: Double { 31 | switch self { 32 | case .initial: 0.0 33 | case .move, .scale: 225.0 34 | } 35 | } 36 | } 37 | 38 | struct MxClausView: View { 39 | @State private var reactionCount = 0 40 | 41 | var body: some View { 42 | HStack { 43 | Text("🧑🏿‍🎄") 44 | .font(.largeTitle) 45 | .phaseAnimator( 46 | MrsClauseScale.allCases 47 | ) { content, phase in 48 | content 49 | .scaleEffect(phase.scale, anchor: .bottom) 50 | .offset(y: phase.verticalOffset) 51 | .hueRotation(.degrees(phase.chromaRotate)) 52 | } animation: { phase in 53 | switch phase { 54 | case .initial: .bouncy(duration: 1.5, extraBounce: 0.25) 55 | case .move: .easeInOut(duration: 0.6).delay(0.29) 56 | case .scale: .spring(duration: 1, bounce: 0.7) 57 | } 58 | } 59 | } 60 | 61 | } 62 | 63 | func nothing() {} 64 | } 65 | 66 | #Preview { 67 | MxClausView() 68 | .preferredColorScheme(.dark) 69 | } 70 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/PresentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentView.swift 3 | // SwiftUIChristmasTree 4 | 5 | import SwiftUI 6 | 7 | struct PresentView: View { 8 | var body: some View { 9 | Text("💝") 10 | .phaseAnimator([false, true]) { AirPodsMax, threeDYRotate in 11 | AirPodsMax 12 | .rotation3DEffect(.degrees(threeDYRotate ? 0 : 360 * 5), axis: (x: 0, y: 1, z: 0)) 13 | } animation: { threeDYRotate in 14 | .linear(duration: 5).speed(0.3).repeatForever(autoreverses: false) 15 | } 16 | } 17 | } 18 | 19 | #Preview { 20 | PresentView() 21 | .preferredColorScheme(.dark) 22 | } 23 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/SwiftUIChristmasTreeApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIChristmasTreeApp.swift 3 | // SwiftUIChristmasTree 4 | 5 | import SwiftUI 6 | 7 | @main 8 | struct SwiftUIChristmasTreeApp: App { 9 | var body: some Scene { 10 | WindowGroup { 11 | SwiftUIXmasTree() 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/SwiftUIXmasTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SwiftUIChristmasTree 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct SwiftUIXmasTree: View { 9 | 10 | @State private var isSpinning = false 11 | let coral = Color(#colorLiteral(red: 1, green: 0.4941176471, blue: 0.4745098039, alpha: 1)) 12 | let peach = Color(#colorLiteral(red: 1, green: 0.831372549, blue: 0.4745098039, alpha: 1)) 13 | let lightLimeGreen = Color(#colorLiteral(red: 0.831372549, green: 0.9843137255, blue: 0.4745098039, alpha: 1)) 14 | let springGreen = Color(#colorLiteral(red: 0.2862745098, green: 0.9803921569, blue: 0.4745098039, alpha: 1)) 15 | let paleAqua = Color(#colorLiteral(red: 0.2862745098, green: 0.9882352941, blue: 0.8392156863, alpha: 1)) 16 | let skyBlue = Color(#colorLiteral(red: 0.2901960784, green: 0.8392156863, blue: 1, alpha: 1)) 17 | let softLavender = Color(#colorLiteral(red: 0.4784313725, green: 0.5058823529, blue: 1, alpha: 1)) 18 | let electricPurple = Color(#colorLiteral(red: 0.8470588235, green: 0.5137254902, blue: 1, alpha: 1)) 19 | let olive = Color(#colorLiteral(red: 0.5764705882, green: 0.3529411765, blue: 0, alpha: 1)) 20 | let forestGreen = Color(#colorLiteral(red: 0, green: 0.5607843137, blue: 0, alpha: 1)) 21 | 22 | var body: some View { 23 | VStack { 24 | //Image(systemName: "wand.and.stars.inverse") 25 | Text("🎁") 26 | .font(.system(size: 64)) 27 | .hueRotation(.degrees(isSpinning ? 0 : 150)) 28 | .animation(.easeInOut(duration: 1).repeatForever(autoreverses: true).delay(0.5), value: isSpinning) 29 | 30 | PresentView() 31 | 32 | ZStack { 33 | ZStack { 34 | Circle() // MARK: One. No delay 35 | .stroke(lineWidth: 1) 36 | .frame(width: 20, height: 20) 37 | .foregroundStyle(coral.gradient) 38 | 39 | ForEach(0 ..< 4) { 40 | //Circle() 41 | Text("✨") 42 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 43 | .offset(y: -10) 44 | .rotationEffect(.degrees(Double($0) * -90)) 45 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 46 | .frame(width: 4, height: 4) 47 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false), value: isSpinning) 48 | } 49 | } 50 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 51 | .offset(y: -160) 52 | 53 | ZStack { 54 | Circle() // MARK: Two. 0.1 delay 55 | .stroke(lineWidth: 2) 56 | .frame(width: 50, height: 50) 57 | .foregroundStyle(peach.gradient) 58 | 59 | ForEach(0 ..< 4) { 60 | //Circle() 61 | Text("🌟") 62 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 63 | .offset(y: -25) 64 | .rotationEffect(.degrees(Double($0) * -90)) 65 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 66 | .frame(width: 6, height: 6) 67 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.1), value: isSpinning) 68 | } 69 | } 70 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 71 | .offset(y: -120) 72 | 73 | ZStack { 74 | Circle() // Three. 0.2 delay 75 | .stroke(lineWidth: 3) 76 | .frame(width: 80, height: 80) 77 | .foregroundStyle(lightLimeGreen.gradient) 78 | 79 | ForEach(0 ..< 4) { 80 | //Circle() 81 | Text("💫") 82 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 83 | .offset(y: -40) 84 | .rotationEffect(.degrees(Double($0) * -90)) 85 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 86 | .frame(width: 8, height: 8) 87 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.2), value: isSpinning) 88 | } 89 | } 90 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 91 | .offset(y: -80) 92 | 93 | ZStack { 94 | Circle() // MARK: Four. 0.3 delay 95 | .stroke(lineWidth: 3) 96 | .frame(width: 110, height: 110) 97 | .foregroundStyle(springGreen.gradient) 98 | 99 | ForEach(0 ..< 4) { 100 | Circle() 101 | .foregroundColor(.red) 102 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 103 | .offset(y: -55) 104 | .rotationEffect(.degrees(Double($0) * -90)) 105 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 106 | .frame(width: 8, height: 8) 107 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.3), value: isSpinning) 108 | } 109 | } 110 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 111 | .offset(y: -40) 112 | 113 | ZStack { 114 | Circle() // MARK: Five. 0.4 delay 115 | .stroke(lineWidth: 3) 116 | .frame(width: 140, height: 140) 117 | .foregroundStyle(paleAqua.gradient) 118 | 119 | ForEach(0 ..< 4) { 120 | Circle() 121 | .foregroundStyle(.red.gradient) 122 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 123 | .offset(y: -70) 124 | .rotationEffect(.degrees(Double($0) * -90)) 125 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 126 | .frame(width: 10, height: 10) 127 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.4), value: isSpinning) 128 | } 129 | } 130 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 131 | .offset(y: 0) 132 | 133 | ZStack { 134 | Circle() // MARK: Six. 0.5 delay 135 | .stroke(lineWidth: 3) 136 | .frame(width: 170, height: 170) 137 | .foregroundStyle(skyBlue.gradient) 138 | 139 | ForEach(0 ..< 4) { 140 | Circle() 141 | .foregroundStyle(.red.gradient) 142 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 143 | .offset(y: -85) 144 | .rotationEffect(.degrees(Double($0) * -90)) 145 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 146 | .frame(width: 8, height: 8) 147 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.5), value: isSpinning) 148 | } 149 | } 150 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 151 | .offset(y: 40) 152 | 153 | ZStack { 154 | Circle() // MARK: Seven. 0.6 delay 155 | .stroke(lineWidth: 5) 156 | .frame(width: 200, height: 200) 157 | .foregroundStyle(softLavender.gradient) 158 | 159 | ForEach(0 ..< 4) { 160 | Image(systemName: "star") 161 | .foregroundStyle(.red.gradient) 162 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 163 | .offset(y: -100) 164 | .rotationEffect(.degrees(Double($0) * -90)) 165 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 166 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.6), value: isSpinning) 167 | } 168 | } 169 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 170 | .offset(y: 80) 171 | 172 | ZStack { 173 | Circle() // MARK: Eight. 0.7 delay 174 | .stroke(lineWidth: 4) 175 | .frame(width: 230, height: 230) 176 | .foregroundStyle(electricPurple.gradient) 177 | 178 | ForEach(0 ..< 4) { 179 | Circle() 180 | .foregroundStyle(.red.gradient) 181 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 182 | .offset(y: -115) 183 | .rotationEffect(.degrees(Double($0) * -90)) 184 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 185 | .frame(width: 10, height: 10) 186 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.7), value: isSpinning) 187 | } 188 | } 189 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 190 | .offset(y: 120) 191 | 192 | ZStack { 193 | Circle() // MARK: Nine. 0.8 delay 194 | .stroke(lineWidth: 5) 195 | .frame(width: 260, height: 260) 196 | .foregroundStyle(olive.gradient) 197 | 198 | ForEach(0 ..< 4) { 199 | //Circle() 200 | Text("🧧") 201 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 202 | .offset(y: -130) 203 | .rotationEffect(.degrees(Double($0) * -90)) 204 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 205 | .frame(width: 12, height: 12) 206 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.8), value: isSpinning) 207 | } 208 | } 209 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 210 | .offset(y: 160) 211 | 212 | 213 | ZStack { 214 | Circle() // MARK: Ten. 0.9 delay 215 | .stroke(lineWidth: 5) 216 | .foregroundStyle(forestGreen.gradient) 217 | 218 | ForEach(0 ..< 4) { 219 | Text("💐") 220 | .rotationEffect(.degrees(-45)) 221 | .hueRotation(.degrees(isSpinning ? Double($0) * 310 : Double($0) * 50)) 222 | .offset(y: -145) 223 | .rotationEffect(.degrees(Double($0) * -90)) 224 | .rotationEffect(.degrees(isSpinning ? 0 : -180)) 225 | .animation(.linear(duration: 1.5).repeatForever(autoreverses: false).delay(0.9), value: isSpinning) 226 | } 227 | } 228 | .frame(width: 290, height: 290) 229 | .rotation3DEffect(.degrees(60), axis: (x: 1, y: 0, z: 0)) 230 | .offset(y: 200) 231 | } 232 | .onAppear() { 233 | isSpinning.toggle() 234 | } 235 | } 236 | } 237 | } 238 | 239 | #Preview { 240 | SwiftUIXmasTree() 241 | .preferredColorScheme(.dark) 242 | } 243 | -------------------------------------------------------------------------------- /SwiftUIChristmasTree/SwiftUIChristmasTree/XmasSurpriseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XmasSurpriseView.swift 3 | // SwiftUIChristmasTree 4 | 5 | import SwiftUI 6 | 7 | struct XmasSurpriseView: View { 8 | var body: some View { 9 | ZStack { 10 | Image(.xmasSurprise) 11 | VStack{ 12 | HStack { 13 | Image(.eyeRight) 14 | .offset(x: -6, y: 22) 15 | .phaseAnimator([false, true]) { mickeyEyeRight, isBlinking in 16 | mickeyEyeRight 17 | .scaleEffect(isBlinking ? 0 : 1) 18 | } animation: { isBlinking in 19 | .timingCurve(0.68, -0.6, 0.32, 1.6).delay(2).repeatForever(autoreverses: false) 20 | } 21 | Image(.eyeLeft) 22 | 23 | .phaseAnimator([false, true]) { mickeyEyeRight, isBlinking in 24 | mickeyEyeRight 25 | .scaleEffect(isBlinking ? 0.5 : 1) 26 | } animation: { isBlinking in 27 | .timingCurve(0.9, -0.45, 0.39, 1.9).delay(2).repeatForever(autoreverses: false) 28 | } 29 | .offset(x: 44, y: 22) 30 | } 31 | 32 | Image(.mouth) 33 | .offset(x: 18, y: 12) 34 | .phaseAnimator([false, true]) { mickeyTongue, isLaughing in 35 | mickeyTongue 36 | .scaleEffect(x: isLaughing ? 0.6 : 0.8, anchor: .bottomTrailing) 37 | .scaleEffect(y: isLaughing ? 0.9 : 1, anchor: .topLeading) 38 | } animation: { isLaughing in 39 | .easeOut.speed(2).delay(0.01).repeatForever(autoreverses: true) 40 | } 41 | } 42 | 43 | } 44 | .frame(width: 48, height: 64) 45 | } 46 | } 47 | 48 | #Preview { 49 | XmasSurpriseView() 50 | .preferredColorScheme(.dark) 51 | } 52 | --------------------------------------------------------------------------------