Skip to content

Commit ea25aeb

Browse files
committed
feat(ios): add state restoration across app lifecycle
1 parent 1bb338c commit ea25aeb

4 files changed

Lines changed: 45 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- iPad keyboard shortcuts (Cmd+N new connection, Cmd+Return execute query, Cmd+1/2 switch tabs) and trackpad hover effects on list rows
1414
- Server Dashboard with active sessions, server metrics, and slow query monitoring (PostgreSQL, MySQL, MSSQL, ClickHouse, DuckDB, SQLite)
1515
- Handoff support for cross-device continuity between iOS and macOS
16+
- State restoration across app lifecycle on iOS (selected connection, active tab, query text, database/schema selection)
1617

1718
## [0.30.1] - 2026-04-10
1819

TableProMobile/TableProMobile/Views/ConnectedView.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ struct ConnectedView: View {
2121
@State private var appError: AppError?
2222
@State private var failureAlertMessage: String?
2323
@State private var showFailureAlert = false
24-
@State private var selectedTab = ConnectedTab.tables
24+
@AppStorage("lastSelectedTab") private var selectedTabRaw: String = ConnectedTab.tables.rawValue
2525
@State private var queryHistory: [QueryHistoryItem] = []
2626
@State private var historyStorage = QueryHistoryStorage()
2727
@State private var databases: [String] = []
28-
@State private var activeDatabase: String = ""
28+
@AppStorage("lastActiveDatabase") private var activeDatabase: String = ""
2929
@State private var schemas: [String] = []
30-
@State private var activeSchema: String = "public"
30+
@AppStorage("lastActiveSchema") private var activeSchema: String = "public"
3131
@State private var isSwitching = false
3232
@State private var isReconnecting = false
3333
@State private var hapticSuccess = false
@@ -40,6 +40,18 @@ struct ConnectedView: View {
4040
case query = "Query"
4141
}
4242

43+
private var selectedTab: ConnectedTab {
44+
get { ConnectedTab(rawValue: selectedTabRaw) ?? .tables }
45+
set { selectedTabRaw = newValue.rawValue }
46+
}
47+
48+
private var selectedTabBinding: Binding<ConnectedTab> {
49+
Binding(
50+
get: { ConnectedTab(rawValue: selectedTabRaw) ?? .tables },
51+
set: { selectedTabRaw = $0.rawValue }
52+
)
53+
}
54+
4355
private var displayName: String {
4456
connection.name.isEmpty ? connection.host : connection.name
4557
}
@@ -119,7 +131,7 @@ struct ConnectedView: View {
119131
.navigationTitle(supportsDatabaseSwitching && databases.count > 1 ? "" : displayName)
120132
.navigationBarTitleDisplayMode(.inline)
121133
.safeAreaInset(edge: .top) {
122-
Picker("Tab", selection: $selectedTab) {
134+
Picker("Tab", selection: selectedTabBinding) {
123135
Text("Tables").tag(ConnectedTab.tables)
124136
Text("Query").tag(ConnectedTab.query)
125137
}

TableProMobile/TableProMobile/Views/ConnectionListView.swift

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ struct ConnectionListView: View {
1212
@Environment(\.horizontalSizeClass) private var sizeClass
1313
@State private var showingAddConnection = false
1414
@State private var editingConnection: DatabaseConnection?
15-
@State private var selectedConnectionId: UUID?
15+
@AppStorage("lastConnectionId") private var selectedConnectionIdString: String?
1616
@State private var columnVisibility: NavigationSplitViewVisibility = .automatic
1717
@State private var showingGroupManagement = false
1818
@State private var showingTagManagement = false
19-
@State private var filterTagId: UUID?
20-
@State private var groupByGroup = false
19+
@AppStorage("lastFilterTagId") private var filterTagIdString: String?
20+
@AppStorage("groupByGroup") private var groupByGroup = false
2121
@State private var editMode: EditMode = .inactive
2222
@State private var connectionToDelete: DatabaseConnection?
2323

@@ -28,6 +28,21 @@ struct ConnectionListView: View {
2828
)
2929
}
3030

31+
private var selectedConnectionId: Binding<UUID?> {
32+
Binding(
33+
get: { selectedConnectionIdString.flatMap { UUID(uuidString: $0) } },
34+
set: { selectedConnectionIdString = $0?.uuidString }
35+
)
36+
}
37+
38+
private var selectedConnectionUUID: UUID? {
39+
selectedConnectionIdString.flatMap { UUID(uuidString: $0) }
40+
}
41+
42+
private var filterTagId: UUID? {
43+
filterTagIdString.flatMap { UUID(uuidString: $0) }
44+
}
45+
3146
private var displayedConnections: [DatabaseConnection] {
3247
var result = appState.connections
3348
if let filterTagId {
@@ -44,8 +59,8 @@ struct ConnectionListView: View {
4459
}
4560

4661
private var selectedConnection: DatabaseConnection? {
47-
guard let selectedConnectionId else { return nil }
48-
return appState.connections.first { $0.id == selectedConnectionId }
62+
guard let id = selectedConnectionUUID else { return nil }
63+
return appState.connections.first { $0.id == id }
4964
}
5065

5166
var body: some View {
@@ -90,7 +105,7 @@ struct ConnectionListView: View {
90105
.onChange(of: appState.pendingConnectionId) { _, newId in
91106
navigateToPendingConnection(newId)
92107
}
93-
.onChange(of: filterTagId) {
108+
.onChange(of: filterTagIdString) {
94109
editMode = .inactive
95110
}
96111
.onChange(of: groupByGroup) {
@@ -135,7 +150,7 @@ struct ConnectionListView: View {
135150

136151
@ViewBuilder
137152
private var connectionList: some View {
138-
let list = List(selection: $selectedConnectionId) {
153+
let list = List(selection: selectedConnectionId) {
139154
if groupByGroup {
140155
groupedContent
141156
} else {
@@ -201,8 +216,8 @@ struct ConnectionListView: View {
201216
) {
202217
Button(String(localized: "Delete"), role: .destructive) {
203218
if let connection = connectionToDelete {
204-
if selectedConnectionId == connection.id {
205-
selectedConnectionId = nil
219+
if selectedConnectionUUID == connection.id {
220+
selectedConnectionIdString = nil
206221
}
207222
appState.removeConnection(connection)
208223
}
@@ -222,7 +237,7 @@ struct ConnectionListView: View {
222237
if !appState.tags.isEmpty {
223238
Section("Filter by Tag") {
224239
Button {
225-
filterTagId = nil
240+
filterTagIdString = nil
226241
} label: {
227242
HStack {
228243
Text("All")
@@ -233,7 +248,7 @@ struct ConnectionListView: View {
233248
}
234249
ForEach(appState.tags) { tag in
235250
Button {
236-
filterTagId = tag.id
251+
filterTagIdString = tag.id.uuidString
237252
} label: {
238253
HStack {
239254
Image(systemName: "circle.fill")
@@ -339,7 +354,7 @@ struct ConnectionListView: View {
339354
private func navigateToPendingConnection(_ id: UUID?) {
340355
guard let id,
341356
appState.connections.contains(where: { $0.id == id }) else { return }
342-
selectedConnectionId = id
357+
selectedConnectionIdString = id.uuidString
343358
appState.pendingConnectionId = nil
344359
}
345360

TableProMobile/TableProMobile/Views/QueryEditorView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct QueryEditorView: View {
1717

1818
private static let logger = Logger(subsystem: "com.TablePro", category: "QueryEditorView")
1919

20-
@State private var query = ""
20+
@AppStorage("lastQueryText") private var query = ""
2121
@State private var result: QueryResult?
2222
@State private var appError: AppError?
2323
@State private var isExecuting = false

0 commit comments

Comments
 (0)