Skip to content

Commit 54a29d6

Browse files
authored
Add ForEach support (#588)
1 parent 30ebb6d commit 54a29d6

File tree

13 files changed

+1775
-73
lines changed

13 files changed

+1775
-73
lines changed

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-
MyViewThatFitsExample()
69+
ForEachExample()
7070
}
7171
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// ForEachUITests.swift
3+
// OpenSwiftUIUITests
4+
5+
import Testing
6+
import SnapshotTesting
7+
8+
@MainActor
9+
@Suite(.snapshots(record: .never, diffTool: diffTool))
10+
struct ForEachUITests {
11+
@Test
12+
func offset() {
13+
struct ContentView: View {
14+
var body: some View {
15+
VStack(spacing: 0) {
16+
ForEach(0 ..< 6) { index in
17+
Color.red.opacity(Double(index) / 6.0 )
18+
}
19+
}
20+
}
21+
}
22+
openSwiftUIAssertSnapshot(of: ContentView())
23+
}
24+
25+
@Test
26+
func keyPath() {
27+
struct ContentView: View {
28+
let opacities = [0, 0.2, 0.4, 0.6, 0.8, 1.0]
29+
30+
var body: some View {
31+
VStack(spacing: 0) {
32+
ForEach(opacities, id: \.self) { opacity in
33+
Color.red.opacity(opacity)
34+
}
35+
}
36+
}
37+
}
38+
openSwiftUIAssertSnapshot(of: ContentView())
39+
}
40+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// ForEachExample.swift
3+
// SharedExample
4+
5+
#if OPENSWIFTUI
6+
import OpenObservation
7+
import OpenSwiftUI
8+
#else
9+
import Observation
10+
import SwiftUI
11+
#endif
12+
13+
struct ForEachExample: View {
14+
var body: some View {
15+
// HStack {
16+
// ForEachOffsetExample()
17+
// ForEachKeyPathExample()
18+
// }
19+
ForEachDynamicView()
20+
}
21+
}
22+
23+
struct ForEachOffsetExample: View {
24+
var body: some View {
25+
VStack(spacing: 0) {
26+
ForEach(0 ..< 6) { index in
27+
Color.red.opacity(Double(index) / 6.0 )
28+
}
29+
}
30+
}
31+
}
32+
33+
struct ForEachKeyPathExample: View {
34+
let opacities = [0, 0.2, 0.4, 0.6, 0.8, 1.0]
35+
36+
var body: some View {
37+
VStack(spacing: 0) {
38+
ForEach(opacities, id: \.self) { opacity in
39+
Color.red.opacity(opacity)
40+
}
41+
}
42+
}
43+
}
44+
45+
// TODO: Animation test case
46+
struct ForEachDynamicView: View {
47+
@State var opacities = [0, 0.5, 1.0]
48+
49+
var body: some View {
50+
VStack(spacing: 0) {
51+
ForEach(opacities, id: \.self) { opacity in
52+
Color.red.opacity(opacity)
53+
}
54+
}.onAppear {
55+
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
56+
withAnimation(.spring) {
57+
opacities.insert(0.25, at: 1)
58+
opacities.insert(0.75, at: 3)
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
struct ForEachNonConstantRangeView: View {
66+
let range = 0..<5
67+
68+
var body: some View {
69+
// Expect to emit warning: "Non-constant range: not an integer range" in compile time using Xcode toolchain
70+
ForEach(range) { _ in
71+
EmptyView()
72+
}
73+
}
74+
}
75+
76+
@Observable
77+
private class ForEachLazyContainerNonConstantCountViewModel {
78+
var data: [Int] = [1, 2]
79+
}
80+
81+
// TODO: LazyContainer (eg. List / LazyHVStack)
82+
struct ForEachLazyContainerNonConstantCountView: View {
83+
private typealias Model = ForEachLazyContainerNonConstantCountViewModel
84+
85+
@State private var model = Model()
86+
87+
var body: some View {
88+
// List {
89+
ForEach(model.data, id: \.self) { i in
90+
if i % 2 == 0 {
91+
Text(i.description)
92+
}
93+
}
94+
// }
95+
}
96+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// LazyContainer.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
struct IsInLazyContainer: ViewInputBoolFlag {}
9+
10+
extension _GraphInputs {
11+
@inline(__always)
12+
package var isInLazyContainer: Bool {
13+
get { self[IsInLazyContainer.self] }
14+
set { self[IsInLazyContainer.self] = newValue }
15+
}
16+
}
17+
18+
extension _ViewInputs {
19+
package mutating func coreConfigureForLazyContainer() {
20+
isInLazyContainer = true
21+
}
22+
23+
package var isInLazyContainer: Bool {
24+
get { base.isInLazyContainer }
25+
set { base.isInLazyContainer = newValue }
26+
}
27+
}
28+
29+
extension _ViewListInputs {
30+
package var isInLazyContainer: Bool {
31+
get { base.isInLazyContainer }
32+
set { base.isInLazyContainer = newValue }
33+
}
34+
}

Sources/OpenSwiftUICore/Semantic/Feature.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ extension Feature {
1818

1919
package protocol UserDefaultKeyedFeature: Feature {
2020
static var key: String { get }
21+
2122
static var defaultFeatureValue: Bool { get }
23+
2224
#if !os(WASI)
2325
static var defaults: UserDefaults { get }
2426
#endif
27+
2528
static var cachedValue: Bool? { get set }
2629
}
2730

@@ -47,6 +50,7 @@ extension UserDefaultKeyedFeature {
4750
}
4851

4952
package static var defaultFeatureValue: Bool { false }
53+
5054
#if !os(WASI)
5155
package static var defaults: UserDefaults { .standard }
5256
#endif
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// UserDefaultsAdditions.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
package import Foundation
9+
10+
extension UserDefaults {
11+
package static let uiKit = UserDefaults(suiteName: "com.apple.UIKit")
12+
13+
package static let openSwiftUI = UserDefaults(suiteName: "org.OpenSwiftUIProject.OpenSwiftUI")
14+
}

Sources/OpenSwiftUICore/View/DynamicViewContent.swift renamed to Sources/OpenSwiftUICore/View/DynamicViewContent/DynamicViewContent.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
// DynamicViewContent.swift
33
// OpenSwiftUICore
44
//
5-
// Audited for 6.0.87
5+
// Audited for 6.5.4
66
// Status: Complete
77

8+
// MARK: - DynamicViewContent
9+
810
/// A type of view that generates views from an underlying collection of data.
911
@available(OpenSwiftUI_v1_0, *)
1012
public protocol DynamicViewContent: View {
13+
1114
/// The type of the underlying collection of data.
1215
associatedtype Data: Collection
1316

@@ -23,6 +26,8 @@ extension ModifiedContent: DynamicViewContent where Content: DynamicViewContent,
2326
}
2427
}
2528

29+
// MARK: - DynamicViewContent Traits
30+
2631
package struct DynamicViewContentIDTraitKey: _ViewTraitKey {
2732
package static let defaultValue: Int? = nil
2833
}

0 commit comments

Comments
 (0)