Skip to content

Commit 8524f82

Browse files
muukiiclaude
andauthored
Tiled (#1)
* WIP * WIP * Update * Refactor TiledViewController to UIView and add attributes cache - Change TiledViewController (UIViewController) to TiledView (UIView) - Simplifies integration as subview without child VC management - UICollectionView wrapper doesn't need full ViewController overhead - Update TiledViewRepresentable to UIViewRepresentable - Rename viewController binding to tiledView - Add layout attributes caching to TiledCollectionViewLayout - New usesAttributesCache option (default: true) - Reuse UICollectionViewLayoutAttributes objects instead of recreating - Only rebuild on bounds size change or explicit clear() - Improves scroll performance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update * Add itemSizeProvider for accurate cell size measurement - Add itemSizeProvider closure to TiledCollectionViewLayout for querying cell sizes - Use TiledViewCell with UIHostingConfiguration for consistent size measurement - Measure sizes using the same method as preferredLayoutAttributesFitting - Add architecture documentation in docs/TiledView-Architecture.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add TiledDataSource for declarative API support - Add TiledDataSource struct with change tracking (prepend, append, update, remove, setItems) - Use id: UUID and changeCounter: Int for cursor-based change consumption - Update TiledView with applyDataSource method to consume pending changes - Simplify TiledViewRepresentable to accept dataSource instead of @binding tiledView - Update Demo to use declarative style with @State dataSource 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Cleanup * WIP * Update * Update * Update * Update --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a03a9e5 commit 8524f82

22 files changed

+2934
-544
lines changed

.claude/settings.local.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"mcp__XcodeBuildMCP__discover_projs",
5+
"mcp__sosumi__searchAppleDocumentation",
6+
"WebFetch(domain:medium.com)",
7+
"Bash(tee:*)"
8+
]
9+
}
10+
}

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Always build for iOS
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
//
2+
// ApplyDiffDemo.swift
3+
// MessagingUIDevelopment
4+
//
5+
// Created by Hiroshi Kimura on 2025/12/12.
6+
//
7+
8+
import SwiftUI
9+
import MessagingUI
10+
11+
// MARK: - ApplyDiff Demo
12+
13+
/// Demonstrates the `applyDiff(from:)` method which automatically detects
14+
/// prepend, append, insert, update, and remove operations from array differences.
15+
struct BookApplyDiffDemo: View {
16+
17+
@State private var dataSource = ListDataSource<ChatMessage>()
18+
19+
/// Source of truth - the "server" data
20+
@State private var serverItems: [ChatMessage] = []
21+
22+
/// Next ID for new items
23+
@State private var nextId = 0
24+
25+
/// Log of operations performed
26+
@State private var operationLog: [String] = []
27+
28+
/// Previous change counter to detect new changes
29+
@State private var previousChangeCounter = 0
30+
31+
@State private var scrollPosition = TiledScrollPosition()
32+
33+
var body: some View {
34+
VStack(spacing: 0) {
35+
// Control Panel
36+
VStack(spacing: 12) {
37+
Text("applyDiff Demo")
38+
.font(.headline)
39+
40+
Text("Modify the 'server' array, then applyDiff auto-detects changes")
41+
.font(.caption)
42+
.foregroundStyle(.secondary)
43+
.multilineTextAlignment(.center)
44+
45+
// Row 1: Basic operations
46+
HStack {
47+
Button("Prepend") {
48+
let newItem = ChatMessage(id: nextId, text: "Prepended #\(nextId)")
49+
nextId += 1
50+
serverItems.insert(newItem, at: 0)
51+
applyAndLog("prepend(1)")
52+
}
53+
.buttonStyle(.bordered)
54+
55+
Button("Append") {
56+
let newItem = ChatMessage(id: nextId, text: "Appended #\(nextId)")
57+
nextId += 1
58+
serverItems.append(newItem)
59+
applyAndLog("append(1)")
60+
}
61+
.buttonStyle(.bordered)
62+
63+
Button("Insert Mid") {
64+
guard serverItems.count >= 2 else {
65+
serverItems.append(ChatMessage(id: nextId, text: "First #\(nextId)"))
66+
nextId += 1
67+
applyAndLog("setItems")
68+
return
69+
}
70+
let midIndex = serverItems.count / 2
71+
let newItem = ChatMessage(id: nextId, text: "Inserted #\(nextId)")
72+
nextId += 1
73+
serverItems.insert(newItem, at: midIndex)
74+
applyAndLog("insert@\(midIndex)(1)")
75+
}
76+
.buttonStyle(.bordered)
77+
}
78+
79+
// Row 2: Update / Remove
80+
HStack {
81+
Button("Update First") {
82+
guard !serverItems.isEmpty else { return }
83+
serverItems[0].text = "Updated! \(Date().formatted(date: .omitted, time: .standard))"
84+
applyAndLog("update(1)")
85+
}
86+
.buttonStyle(.bordered)
87+
88+
Button("Remove Last") {
89+
guard !serverItems.isEmpty else { return }
90+
serverItems.removeLast()
91+
applyAndLog("remove(1)")
92+
}
93+
.buttonStyle(.bordered)
94+
95+
Button("Shuffle") {
96+
serverItems.shuffle()
97+
applyAndLog("shuffle→setItems")
98+
}
99+
.buttonStyle(.bordered)
100+
}
101+
102+
// Row 3: Complex operations
103+
HStack {
104+
Button("Prepend+Update+Remove") {
105+
guard serverItems.count >= 2 else {
106+
// Initialize with some items
107+
serverItems = [
108+
ChatMessage(id: nextId, text: "Item A"),
109+
ChatMessage(id: nextId + 1, text: "Item B"),
110+
ChatMessage(id: nextId + 2, text: "Item C"),
111+
]
112+
nextId += 3
113+
applyAndLog("setItems(3)")
114+
return
115+
}
116+
117+
// Prepend new item
118+
let newItem = ChatMessage(id: nextId, text: "New Prepended #\(nextId)")
119+
nextId += 1
120+
serverItems.insert(newItem, at: 0)
121+
122+
// Update second item (was first before prepend)
123+
if serverItems.count > 1 {
124+
serverItems[1].text = "Updated!"
125+
}
126+
127+
// Remove last item
128+
serverItems.removeLast()
129+
130+
applyAndLog("remove+prepend+update")
131+
}
132+
.buttonStyle(.bordered)
133+
.tint(.orange)
134+
135+
Button("Reset") {
136+
serverItems = []
137+
nextId = 0
138+
operationLog = []
139+
previousChangeCounter = 0
140+
dataSource = ListDataSource()
141+
}
142+
.buttonStyle(.borderedProminent)
143+
.tint(.red)
144+
}
145+
146+
// Stats
147+
HStack {
148+
Text("Items: \(serverItems.count)")
149+
Spacer()
150+
Text("ChangeCounter: \(dataSource.changeCounter)")
151+
}
152+
.font(.caption)
153+
.foregroundStyle(.secondary)
154+
}
155+
.padding()
156+
.background(Color(.systemBackground))
157+
158+
Divider()
159+
160+
// Operation Log
161+
VStack(alignment: .leading, spacing: 4) {
162+
Text("Operation Log (expected changes):")
163+
.font(.caption.bold())
164+
ScrollView(.horizontal, showsIndicators: false) {
165+
HStack(spacing: 8) {
166+
ForEach(operationLog.suffix(10), id: \.self) { log in
167+
Text(log)
168+
.font(.caption2)
169+
.padding(.horizontal, 8)
170+
.padding(.vertical, 4)
171+
.background(logColor(for: log).opacity(0.2))
172+
.clipShape(RoundedRectangle(cornerRadius: 4))
173+
}
174+
}
175+
}
176+
}
177+
.padding(.horizontal)
178+
.padding(.vertical, 8)
179+
.background(Color(.secondarySystemBackground))
180+
181+
Divider()
182+
183+
// List View
184+
TiledView(
185+
dataSource: dataSource,
186+
scrollPosition: $scrollPosition,
187+
cellBuilder: { message, _ in
188+
ChatBubbleView(message: message)
189+
}
190+
)
191+
}
192+
}
193+
194+
private func applyAndLog(_ expectedChange: String) {
195+
var updatedDataSource = dataSource
196+
updatedDataSource.applyDiff(from: serverItems)
197+
198+
// Check if change counter increased
199+
let newCounter = updatedDataSource.changeCounter
200+
if newCounter > previousChangeCounter {
201+
operationLog.append(expectedChange)
202+
previousChangeCounter = newCounter
203+
}
204+
205+
dataSource = updatedDataSource
206+
}
207+
208+
private func logColor(for log: String) -> Color {
209+
if log.contains("prepend") { return .blue }
210+
if log.contains("append") { return .green }
211+
if log.contains("insert") { return .purple }
212+
if log.contains("update") { return .orange }
213+
if log.contains("remove") { return .red }
214+
if log.contains("setItems") || log.contains("shuffle") { return .gray }
215+
return .primary
216+
}
217+
}
218+
219+
#Preview("ApplyDiff Demo") {
220+
BookApplyDiffDemo()
221+
}

Dev/MessagingUIDevelopment/BookChat.swift

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

0 commit comments

Comments
 (0)