Skip to content

Commit 19292ad

Browse files
committed
feat(components): add LazyVGrid
1 parent df6a8eb commit 19292ad

File tree

20 files changed

+332
-38
lines changed

20 files changed

+332
-38
lines changed

example/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import React from 'react';
22
import {BasicFormExample} from './src/views/BasicFormExample';
33
import {FullFormExample} from './src/views/FullFormExample';
4+
import {LayoutExample} from './src/views/LayoutExample';
45
import {WorkoutExample} from './src/views/WorkoutExample.private';
56
import {ReactHookFormExample} from './src/views/ReactHookFormExample';
67
import {TanStackFormExample} from './src/views/TanStackFormExample';
78
import {AdvancedFormExample} from './src/views/AdvancedFormExample.private';
89

910
function App(): React.JSX.Element {
10-
return <FullFormExample />;
11+
return <WorkoutExample />;
1112
}
1213

1314
export default App;

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,7 @@ PODS:
15251525
- React-logger (= 0.78.0)
15261526
- React-perflogger (= 0.78.0)
15271527
- React-utils (= 0.78.0)
1528-
- RNSwiftUI (0.2.3):
1528+
- RNSwiftUI (0.3.0):
15291529
- DoubleConversion
15301530
- glog
15311531
- hermes-engine
@@ -1827,7 +1827,7 @@ SPEC CHECKSUMS:
18271827
ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8
18281828
ReactCodegen: 008c319179d681a6a00966edfc67fda68f9fbb2e
18291829
ReactCommon: 0c097b53f03d6bf166edbcd0915da32f3015dd90
1830-
RNSwiftUI: be3460c08b493866bd2a4729d0d3de6a39bc467f
1830+
RNSwiftUI: 2b5862c12273ae79a3f82313c272762a60399579
18311831
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
18321832
Yoga: afd04ff05ebe0121a00c468a8a3c8080221cb14c
18331833

example/src/types/import-png.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '*.png' {
2+
const value: number;
3+
export default value;
4+
}

example/src/views/FullFormExample.tsx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
import {SwiftUI} from '@mgcrea/react-native-swiftui/src';
22
import {useState, type FunctionComponent} from 'react';
3-
import {Image, View} from 'react-native';
3+
import {View} from 'react-native';
44
import logoImage from '../assets/logo.png';
55

6-
console.log({
7-
logoImage,
8-
resolvedLogoImage: Image.resolveAssetSource(logoImage),
9-
});
10-
116
export const FullFormExample: FunctionComponent = () => {
127
return (
138
<View style={{flex: 1}}>
149
<SwiftUI style={{flex: 1}}>
1510
<SwiftUI.Text text="FullFormExample" />
1611
<SwiftUI.Form>
17-
<ImageSection />
18-
<RectangleSection />
12+
<LazyVGridSection />
1913
<TextFieldSection />
2014
<PickerSection />
2115
<DatePickerSection />
2216
<StepperSection />
23-
<ToggleSection />
2417
<SliderSection />
18+
<ToggleSection />
19+
<ImageSection />
20+
<RectangleSection />
2521
</SwiftUI.Form>
2622
</SwiftUI>
2723
</View>
@@ -201,3 +197,22 @@ const ImageSection: FunctionComponent = () => {
201197
</SwiftUI.Section>
202198
);
203199
};
200+
201+
const LazyVGridSection: FunctionComponent = () => {
202+
return (
203+
<SwiftUI.Section header="LazyVGrid Example">
204+
<SwiftUI.LazyVGrid
205+
columns={[
206+
{type: 'flexible', minimum: 100},
207+
{type: 'flexible', minimum: 100},
208+
]}
209+
spacing={10}
210+
alignment="center">
211+
<SwiftUI.Text text="Item 1" />
212+
<SwiftUI.Text text="Item 2" />
213+
<SwiftUI.Text text="Item 3" />
214+
<SwiftUI.Text text="Item 4" />
215+
</SwiftUI.LazyVGrid>
216+
</SwiftUI.Section>
217+
);
218+
};
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {NativeLazyVGridProps, SwiftUI} from '@mgcrea/react-native-swiftui/src';
2+
import {useState, type FunctionComponent} from 'react';
3+
import {View} from 'react-native';
4+
import logoImage from '../assets/logo.png';
5+
6+
export const LayoutExample: FunctionComponent = () => {
7+
return (
8+
<View style={{flex: 1}}>
9+
<SwiftUI style={{flex: 1}}>
10+
<SwiftUI.Text text="LayoutExample" />
11+
<SwiftUI.Form>
12+
<LazyVGridSection />
13+
<ImageSection />
14+
<RectangleSection />
15+
</SwiftUI.Form>
16+
</SwiftUI>
17+
</View>
18+
);
19+
};
20+
21+
const RectangleSection: FunctionComponent = () => {
22+
const [color, setColor] = useState('blue');
23+
24+
return (
25+
<SwiftUI.Section header="Rectangle Example">
26+
<SwiftUI.HStack
27+
spacing={25}
28+
style={{
29+
backgroundColor: 'white',
30+
borderWidth: 1,
31+
borderColor: 'black',
32+
}}>
33+
<SwiftUI.Rectangle
34+
style={{backgroundColor: color, width: 25, height: 50}}
35+
/>
36+
<SwiftUI.Rectangle
37+
style={{backgroundColor: 'red', width: 25, height: 50}}
38+
/>
39+
</SwiftUI.HStack>
40+
41+
<SwiftUI.Button
42+
title={`Change flag to ${color === 'blue' ? 'Italy' : 'France'}`}
43+
onPress={() => setColor(color === 'blue' ? 'green' : 'blue')}
44+
/>
45+
</SwiftUI.Section>
46+
);
47+
};
48+
49+
const ImageSection: FunctionComponent = () => {
50+
const [icon, setIcon] = useState('iphone');
51+
52+
return (
53+
<SwiftUI.Section header="Image Example">
54+
<SwiftUI.HStack>
55+
<SwiftUI.Image
56+
source={logoImage}
57+
resizeMode="contain"
58+
style={{
59+
width: 128,
60+
height: 128,
61+
borderWidth: 1,
62+
borderColor: 'blue',
63+
borderRadius: 64,
64+
}}
65+
/>
66+
<SwiftUI.Image
67+
name={`system:${icon}`}
68+
// tintColor="#FF0000"
69+
style={{width: 128, height: 128, fontSize: 64, color: 'blue'}}
70+
/>
71+
</SwiftUI.HStack>
72+
<SwiftUI.Button
73+
title={`Change icon to ${icon === 'iphone' ? 'ipad' : 'iphone'}`}
74+
onPress={() => setIcon(icon === 'iphone' ? 'ipad' : 'iphone')}
75+
/>
76+
</SwiftUI.Section>
77+
);
78+
};
79+
80+
const LazyVGridSection: FunctionComponent = () => {
81+
const alignmentValues: NativeLazyVGridProps['alignment'][] = [
82+
'leading',
83+
'center',
84+
'trailing',
85+
];
86+
const [index, setIndex] = useState(1);
87+
88+
return (
89+
<SwiftUI.Section header="LazyVGrid Example">
90+
<SwiftUI.LazyVGrid
91+
columns={[
92+
{type: 'flexible', minimum: 100},
93+
{type: 'flexible', minimum: 100},
94+
]}
95+
spacing={10}
96+
alignment={alignmentValues[index]}>
97+
<SwiftUI.Text text="Item 1" />
98+
<SwiftUI.Text text="Item 2" />
99+
<SwiftUI.Text text="Item 3" />
100+
<SwiftUI.Text text="Item 4" />
101+
</SwiftUI.LazyVGrid>
102+
<SwiftUI.Button
103+
title={`Change alignment to ${
104+
alignmentValues[(index + 1) % alignmentValues.length]
105+
}`}
106+
onPress={() => {
107+
setIndex((index + 1) % alignmentValues.length);
108+
}}
109+
/>
110+
</SwiftUI.Section>
111+
);
112+
};

ios/SwiftUINode.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ private struct NodeWrapper: Decodable {
9090
node = try GenericNode<SpacerProps>(from: decoder)
9191
case "Image":
9292
node = try GenericNode<ImageProps>(from: decoder)
93+
case "LazyVGrid":
94+
node = try GenericNode<LazyVGridProps>(from: decoder)
9395
default:
9496
throw DecodingError.typeMismatch(
9597
(any SwiftUINode).self,

ios/SwiftUIRootView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ public class SwiftUIRootView: SwiftUIContainerView {
166166
}
167167
}
168168
}))
169+
case let lazyVGrid as GenericNode<LazyVGridProps>:
170+
AnyView(LazyVGridView(props: lazyVGrid.props, content: {
171+
if let children = lazyVGrid.children {
172+
ForEach(children, id: \.id) { child in
173+
self.buildSwiftUIView(from: child)
174+
}
175+
}
176+
}))
169177
case let text as GenericNode<TextProps>:
170178
AnyView(TextView(props: text.props))
171179
case let textField as GenericNode<TextFieldProps>:
@@ -271,6 +279,10 @@ public class SwiftUIRootView: SwiftUIContainerView {
271279
let updatedProps = try decoder.decode(ImageProps.self, from: updatedPropsData)
272280
image.props.merge(from: updatedProps)
273281

282+
case let lazyVGrid as GenericNode<LazyVGridProps>:
283+
let updatedProps = try decoder.decode(LazyVGridProps.self, from: updatedPropsData)
284+
lazyVGrid.props.merge(from: updatedProps)
285+
274286
default:
275287
print("Unsupported node type for updateChildProps: \(type(of: node))")
276288
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import SwiftUI
2+
3+
public struct GridItemConfig: Decodable {
4+
let type: String // "fixed", "flexible", "adaptive"
5+
let fixed: CGFloat?
6+
let minimum: CGFloat?
7+
let maximum: CGFloat?
8+
let spacing: CGFloat?
9+
let alignment: String?
10+
11+
enum CodingKeys: String, CodingKey {
12+
case type, fixed, minimum, maximum, spacing, alignment
13+
}
14+
15+
func toGridItem() -> GridItem {
16+
let size: GridItem.Size
17+
switch type {
18+
case "fixed":
19+
size = .fixed(fixed ?? 0)
20+
case "flexible":
21+
size = .flexible(minimum: minimum ?? 10, maximum: maximum ?? .infinity)
22+
case "adaptive":
23+
size = .adaptive(minimum: minimum ?? 0, maximum: maximum ?? .infinity)
24+
default:
25+
size = .flexible() // Fallback
26+
}
27+
let alignmentValue = alignment.flatMap { Alignment(from: $0) }
28+
return GridItem(size, spacing: spacing, alignment: alignmentValue)
29+
}
30+
}
31+
32+
// Helper to map string alignment to SwiftUI Alignment
33+
private extension Alignment {
34+
init?(from string: String) {
35+
switch string {
36+
case "leading": self = .leading
37+
case "center": self = .center
38+
case "trailing": self = .trailing
39+
default: return nil
40+
}
41+
}
42+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import SwiftUI
2+
3+
public final class LazyVGridProps: ObservableObject, Decodable {
4+
@Published public var columns: [GridItemConfig]
5+
@Published public var spacing: CGFloat?
6+
@Published public var alignment: HorizontalAlignment?
7+
@Published public var style: StyleProps?
8+
9+
enum CodingKeys: String, CodingKey {
10+
case columns, spacing, alignment, style
11+
}
12+
13+
public required init(from decoder: Decoder) throws {
14+
let container = try decoder.container(keyedBy: CodingKeys.self)
15+
columns = try container.decode([GridItemConfig].self, forKey: .columns)
16+
spacing = try container.decodeIfPresent(CGFloat.self, forKey: .spacing)
17+
// Decode alignment
18+
if let alignmentString = try container.decodeIfPresent(String.self, forKey: .alignment) {
19+
switch alignmentString {
20+
case "leading": alignment = .leading
21+
case "trailing": alignment = .trailing
22+
default: alignment = .center
23+
}
24+
}
25+
style = try container.decodeIfPresent(StyleProps.self, forKey: .style)
26+
}
27+
28+
public func merge(from other: LazyVGridProps) {
29+
columns = other.columns
30+
spacing = other.spacing
31+
alignment = other.alignment
32+
style = other.style
33+
}
34+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import SwiftUI
2+
3+
public struct LazyVGridView<Content: View>: View {
4+
@ObservedObject public var props: LazyVGridProps
5+
let content: () -> Content
6+
7+
public init(props: LazyVGridProps, @ViewBuilder content: @escaping () -> Content) {
8+
self.props = props
9+
self.content = content
10+
}
11+
12+
public var body: some View {
13+
LazyVGrid(
14+
columns: props.columns.map { $0.toGridItem() },
15+
alignment: props.alignment ?? .center,
16+
spacing: props.spacing
17+
) {
18+
content()
19+
}
20+
.applyViewStyles(props.style)
21+
}
22+
}

0 commit comments

Comments
 (0)