Skip to content

Commit 1f2bea8

Browse files
authored
Add Timeline and Animation functionality (#500)
1 parent 2390cbe commit 1f2bea8

File tree

26 files changed

+2266
-100
lines changed

26 files changed

+2266
-100
lines changed

.vscode/mcp.json

Lines changed: 0 additions & 32 deletions
This file was deleted.

CLAUDE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,23 @@ The project uses **swift-testing framework** (not XCTest):
9494
- Use `@Test` attribute for test functions
9595
- Use `#expect()` for assertions instead of XCTest assertions
9696
- No comments in test case bodies - keep tests clean and self-explanatory
97+
- For floating-point comparisons, use `isApproximatelyEqual` instead of `==` to handle precision issues:
98+
```swift
99+
#expect(value.isApproximatelyEqual(to: expectedValue))
100+
```
101+
102+
### Compatibility Tests
103+
104+
When writing tests in `OpenSwiftUICompatibilityTests`:
105+
- **DO NOT add conditional imports** - imports are handled in `Export.swift`
106+
- **NEVER use module-qualified types** (e.g., `SwiftUI.PeriodicTimelineSchedule`)
107+
- Write test code that works identically with both SwiftUI and OpenSwiftUI
108+
- Simply use types directly without any module prefixes:
109+
```swift
110+
// No conditional imports needed - Export.swift handles this
111+
let schedule = PeriodicTimelineSchedule(from: startDate, by: interval)
112+
let entries = schedule.entries(from: queryDate, mode: .normal)
113+
```
97114

98115
### Code Style (from .github/copilot-instructions.md)
99116

Example/Example.xcodeproj/project.pbxproj

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,28 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
27186AE02D538A6B009E05F9 /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
1110
27186AE32D538A76009E05F9 /* AttributeGraph.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D12D2842740010502F /* AttributeGraph.xcframework */; };
1211
278EF52D2E2272F2009C32EB /* Equatable in Frameworks */ = {isa = PBXBuildFile; productRef = 278EF52C2E2272F2009C32EB /* Equatable */; };
1312
278EF52F2E227304009C32EB /* Equatable in Frameworks */ = {isa = PBXBuildFile; productRef = 278EF52E2E227304009C32EB /* Equatable */; };
1413
279284972DFF136E00234D64 /* AttributeGraph.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D12D2842740010502F /* AttributeGraph.xcframework */; };
15-
279284982DFF136E00234D64 /* AttributeGraph.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D12D2842740010502F /* AttributeGraph.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16-
279284992DFF136E00234D64 /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
17-
2792849A2DFF136E00234D64 /* CoreUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18-
2792849B2DFF136E00234D64 /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
19-
2792849C2DFF136E00234D64 /* RenderBox.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2014
2792849F2DFF137400234D64 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 2792849E2DFF137400234D64 /* SnapshotTesting */; };
2115
279FED052DF4566D00320390 /* AttributeGraph.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D12D2842740010502F /* AttributeGraph.xcframework */; };
22-
279FED062DF4566D00320390 /* AttributeGraph.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D12D2842740010502F /* AttributeGraph.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
23-
279FED082DF4567000320390 /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
24-
279FED092DF4567000320390 /* CoreUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
25-
279FED0A2DF4567400320390 /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
26-
279FED0B2DF4567400320390 /* RenderBox.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2716
279FED0D2DF4567B00320390 /* OpenSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 279FED0C2DF4567B00320390 /* OpenSwiftUI */; };
17+
27AF22B22E758F2900D534AB /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
18+
27AF22B32E758F2900D534AB /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
19+
27AF22B42E758F2900D534AB /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
20+
27AF22B52E758F2900D534AB /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
21+
27AF22B62E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27AF22B12E758F0F00D534AB /* BacklightServices.xcframework */; platformFilters = (ios, xros, ); };
22+
27AF22B72E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27AF22B12E758F0F00D534AB /* BacklightServices.xcframework */; platformFilters = (ios, xros, ); };
23+
27AF22B82E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27AF22B12E758F0F00D534AB /* BacklightServices.xcframework */; platformFilters = (ios, xros, ); };
24+
27AF22B92E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27AF22B12E758F0F00D534AB /* BacklightServices.xcframework */; platformFilters = (ios, xros, ); };
25+
27AF22BA2E758F3700D534AB /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
26+
27AF22BB2E758F3700D534AB /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
27+
27AF22BC2E758F3700D534AB /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
28+
27AF22BD2E758F3700D534AB /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
2829
27CD0B5F2AFC8DA7003665EB /* OpenSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 27CD0B5E2AFC8DA7003665EB /* OpenSwiftUI */; };
2930
27D49E0E2BA60AF600F6E2E2 /* OpenSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 27D49E0D2BA60AF600F6E2E2 /* OpenSwiftUI */; };
3031
27E6C4D32D2842740010502F /* AttributeGraph.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D12D2842740010502F /* AttributeGraph.xcframework */; };
31-
27E6C4D62D2842810010502F /* RenderBox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E6C4D42D2842810010502F /* RenderBox.xcframework */; };
32-
27EE91732DD0C753006C85FD /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
33-
27EE91762DD0C77E006C85FD /* CoreUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27EE91722DD0C753006C85FD /* CoreUI.xcframework */; };
3432
/* End PBXBuildFile section */
3533

3634
/* Begin PBXContainerItemProxy section */
@@ -50,39 +48,11 @@
5048
};
5149
/* End PBXContainerItemProxy section */
5250

53-
/* Begin PBXCopyFilesBuildPhase section */
54-
2792849D2DFF136E00234D64 /* Embed Frameworks */ = {
55-
isa = PBXCopyFilesBuildPhase;
56-
buildActionMask = 2147483647;
57-
dstPath = "";
58-
dstSubfolderSpec = 10;
59-
files = (
60-
2792849A2DFF136E00234D64 /* CoreUI.xcframework in Embed Frameworks */,
61-
279284982DFF136E00234D64 /* AttributeGraph.xcframework in Embed Frameworks */,
62-
2792849C2DFF136E00234D64 /* RenderBox.xcframework in Embed Frameworks */,
63-
);
64-
name = "Embed Frameworks";
65-
runOnlyForDeploymentPostprocessing = 0;
66-
};
67-
279FED072DF4566D00320390 /* Embed Frameworks */ = {
68-
isa = PBXCopyFilesBuildPhase;
69-
buildActionMask = 2147483647;
70-
dstPath = "";
71-
dstSubfolderSpec = 10;
72-
files = (
73-
279FED092DF4567000320390 /* CoreUI.xcframework in Embed Frameworks */,
74-
279FED062DF4566D00320390 /* AttributeGraph.xcframework in Embed Frameworks */,
75-
279FED0B2DF4567400320390 /* RenderBox.xcframework in Embed Frameworks */,
76-
);
77-
name = "Embed Frameworks";
78-
runOnlyForDeploymentPostprocessing = 0;
79-
};
80-
/* End PBXCopyFilesBuildPhase section */
81-
8251
/* Begin PBXFileReference section */
8352
271D81642BB1E8E300A6D543 /* OpenAttributeGraph */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OpenAttributeGraph; path = ../../OpenAttributeGraph; sourceTree = "<group>"; };
8453
275751E32DEE1441003E467C /* TestingHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestingHost.app; sourceTree = BUILT_PRODUCTS_DIR; };
8554
279283B92DFF11CE00234D64 /* OpenSwiftUIUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenSwiftUIUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
55+
27AF22B12E758F0F00D534AB /* BacklightServices.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BacklightServices.xcframework; path = ../../DarwinPrivateFrameworks/BLS/2024/BacklightServices.xcframework; sourceTree = "<group>"; };
8656
27B7FC802BB31FF500272BA5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
8757
27CD0B492AFC8D37003665EB /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
8858
27CD0B612AFC8E0E003665EB /* OpenSwiftUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OpenSwiftUI; path = ..; sourceTree = "<group>"; };
@@ -133,44 +103,48 @@
133103
buildActionMask = 2147483647;
134104
files = (
135105
279FED0D2DF4567B00320390 /* OpenSwiftUI in Frameworks */,
136-
279FED082DF4567000320390 /* CoreUI.xcframework in Frameworks */,
106+
27AF22B82E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */,
107+
27AF22B42E758F2900D534AB /* CoreUI.xcframework in Frameworks */,
108+
27AF22BC2E758F3700D534AB /* RenderBox.xcframework in Frameworks */,
137109
279FED052DF4566D00320390 /* AttributeGraph.xcframework in Frameworks */,
138-
279FED0A2DF4567400320390 /* RenderBox.xcframework in Frameworks */,
139110
);
140111
runOnlyForDeploymentPostprocessing = 0;
141112
};
142113
279283B62DFF11CE00234D64 /* Frameworks */ = {
143114
isa = PBXFrameworksBuildPhase;
144115
buildActionMask = 2147483647;
145116
files = (
146-
279284992DFF136E00234D64 /* CoreUI.xcframework in Frameworks */,
147117
2792849F2DFF137400234D64 /* SnapshotTesting in Frameworks */,
118+
27AF22B92E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */,
119+
27AF22B52E758F2900D534AB /* CoreUI.xcframework in Frameworks */,
120+
27AF22BD2E758F3700D534AB /* RenderBox.xcframework in Frameworks */,
148121
279284972DFF136E00234D64 /* AttributeGraph.xcframework in Frameworks */,
149-
2792849B2DFF136E00234D64 /* RenderBox.xcframework in Frameworks */,
150122
);
151123
runOnlyForDeploymentPostprocessing = 0;
152124
};
153125
27CD0B462AFC8D37003665EB /* Frameworks */ = {
154126
isa = PBXFrameworksBuildPhase;
155127
buildActionMask = 2147483647;
156128
files = (
129+
27AF22B22E758F2900D534AB /* CoreUI.xcframework in Frameworks */,
130+
27AF22B62E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */,
157131
27CD0B5F2AFC8DA7003665EB /* OpenSwiftUI in Frameworks */,
158-
27E6C4D62D2842810010502F /* RenderBox.xcframework in Frameworks */,
159132
278EF52F2E227304009C32EB /* Equatable in Frameworks */,
160-
27EE91762DD0C77E006C85FD /* CoreUI.xcframework in Frameworks */,
161133
27E6C4D32D2842740010502F /* AttributeGraph.xcframework in Frameworks */,
134+
27AF22BA2E758F3700D534AB /* RenderBox.xcframework in Frameworks */,
162135
);
163136
runOnlyForDeploymentPostprocessing = 0;
164137
};
165138
27D49DF52BA604FB00F6E2E2 /* Frameworks */ = {
166139
isa = PBXFrameworksBuildPhase;
167140
buildActionMask = 2147483647;
168141
files = (
142+
27AF22B32E758F2900D534AB /* CoreUI.xcframework in Frameworks */,
143+
27AF22B72E758F2E00D534AB /* BacklightServices.xcframework in Frameworks */,
169144
27D49E0E2BA60AF600F6E2E2 /* OpenSwiftUI in Frameworks */,
170-
27EE91732DD0C753006C85FD /* CoreUI.xcframework in Frameworks */,
171145
278EF52D2E2272F2009C32EB /* Equatable in Frameworks */,
172-
27186AE02D538A6B009E05F9 /* RenderBox.xcframework in Frameworks */,
173146
27186AE32D538A76009E05F9 /* AttributeGraph.xcframework in Frameworks */,
147+
27AF22BB2E758F3700D534AB /* RenderBox.xcframework in Frameworks */,
174148
);
175149
runOnlyForDeploymentPostprocessing = 0;
176150
};
@@ -211,6 +185,7 @@
211185
27D49E0C2BA60AF600F6E2E2 /* Frameworks */ = {
212186
isa = PBXGroup;
213187
children = (
188+
27AF22B12E758F0F00D534AB /* BacklightServices.xcframework */,
214189
27EE91722DD0C753006C85FD /* CoreUI.xcframework */,
215190
27E6C4D42D2842810010502F /* RenderBox.xcframework */,
216191
27E6C4D12D2842740010502F /* AttributeGraph.xcframework */,
@@ -228,7 +203,6 @@
228203
275751DF2DEE1441003E467C /* Sources */,
229204
275751E02DEE1441003E467C /* Frameworks */,
230205
275751E12DEE1441003E467C /* Resources */,
231-
279FED072DF4566D00320390 /* Embed Frameworks */,
232206
);
233207
buildRules = (
234208
);
@@ -252,7 +226,6 @@
252226
279283B52DFF11CE00234D64 /* Sources */,
253227
279283B62DFF11CE00234D64 /* Frameworks */,
254228
279283B72DFF11CE00234D64 /* Resources */,
255-
2792849D2DFF136E00234D64 /* Embed Frameworks */,
256229
);
257230
buildRules = (
258231
);

Example/HostingExample/ViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@ class ViewController: NSViewController {
6666

6767
struct ContentView: View {
6868
var body: some View {
69-
ObservationExample()
69+
AnimatedColorTimelineView()
7070
}
7171
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// AnimatedColorTimelineView.swift
3+
// SharedExample
4+
//
5+
// Created by Kyle on 2025/9/15.
6+
//
7+
8+
#if OPENSWIFTUI
9+
import OpenSwiftUI
10+
#else
11+
import SwiftUI
12+
#endif
13+
14+
#if OPENSWIFTUI
15+
// FIXME: Missing LinearGradient, Shape, Text and safeArea.
16+
// We use a simplified version for OpenSwiftUI now.
17+
struct AnimatedColorTimelineView: View {
18+
var body: some View {
19+
TimelineView(.animation) { context in
20+
let time = context.date.timeIntervalSince1970
21+
22+
ZStack {
23+
// Animated background color
24+
Color(
25+
hue: (sin(time * 0.5) + 1) / 2,
26+
saturation: 0.8,
27+
brightness: 0.9
28+
)
29+
// .ignoresSafeArea()
30+
31+
VStack(spacing: 30) {
32+
// Text("Animated Colors")
33+
// .font(.largeTitle)
34+
// .fontWeight(.bold)
35+
// .foregroundColor(.white)
36+
37+
// Pulsing circle that changes color
38+
// Circle()
39+
// .fill(
40+
Color(
41+
hue: (cos(time * 2) + 1) / 2,
42+
saturation: 1.0,
43+
brightness: 1.0
44+
)
45+
// )
46+
.frame(
47+
width: 100 + sin(time * 3) * 20,
48+
height: 100 + sin(time * 3) * 20
49+
)
50+
51+
// Display current color values
52+
let currentHue = (sin(time * 0.5) + 1) / 2
53+
let _ = print(currentHue)
54+
// Text("Background Hue: \(currentHue, specifier: "%.2f")")
55+
// .font(.headline)
56+
// .foregroundColor(.white)
57+
// .padding()
58+
// .background(Color.black.opacity(0.3))
59+
// .cornerRadius(10)
60+
}
61+
}
62+
}
63+
}
64+
}
65+
#else
66+
struct AnimatedColorTimelineView: View {
67+
var body: some View {
68+
TimelineView(.animation) { timeline in
69+
let time = timeline.date.timeIntervalSince1970
70+
71+
ZStack {
72+
// Animated gradient background
73+
LinearGradient(
74+
colors: [
75+
Color(
76+
hue: (sin(time * 0.5) + 1) / 2,
77+
saturation: 0.8,
78+
brightness: 0.9
79+
),
80+
Color(
81+
hue: (cos(time * 0.3) + 1) / 2,
82+
saturation: 0.6,
83+
brightness: 0.7
84+
),
85+
Color(
86+
hue: (sin(time * 0.7 + .pi) + 1) / 2,
87+
saturation: 0.9,
88+
brightness: 0.8
89+
)
90+
],
91+
startPoint: .topLeading,
92+
endPoint: .bottomTrailing
93+
)
94+
.ignoresSafeArea()
95+
96+
// Content overlay
97+
VStack(spacing: 30) {
98+
Text("Animated Colors")
99+
.font(.largeTitle)
100+
.fontWeight(.bold)
101+
.foregroundColor(.white)
102+
.shadow(radius: 5)
103+
104+
// Pulsing circle
105+
Circle()
106+
.fill(
107+
Color(
108+
hue: (sin(time * 2) + 1) / 2,
109+
saturation: 1.0,
110+
brightness: 1.0
111+
)
112+
)
113+
.frame(
114+
width: 100 + sin(time * 3) * 20,
115+
height: 100 + sin(time * 3) * 20
116+
)
117+
.shadow(radius: 10)
118+
119+
// Color info
120+
let currentHue = (sin(time * 0.5) + 1) / 2
121+
Text("Hue: \(currentHue, specifier: "%.2f")")
122+
.font(.headline)
123+
.foregroundColor(.white)
124+
.padding()
125+
.background(.ultraThinMaterial)
126+
.cornerRadius(10)
127+
}
128+
}
129+
}
130+
}
131+
}
132+
#endif

0 commit comments

Comments
 (0)