Signed-off-by: Matt Bruce <matt.bruce1@toyota.com>
This commit is contained in:
parent
88e4402d38
commit
f3dd8d92bd
@ -8,13 +8,9 @@ globs: ["**/*.swift", "**/*.xcstrings"]
|
|||||||
|
|
||||||
Use **String Catalogs** (`.xcstrings` files) for localization in modern Swift projects.
|
Use **String Catalogs** (`.xcstrings` files) for localization in modern Swift projects.
|
||||||
|
|
||||||
## Required Language Support
|
## Language Support
|
||||||
|
|
||||||
At minimum, support these languages:
|
At minimum, support the base development language (typically English). Add additional locales based on your app's target markets. Common additions include Spanish and French variants.
|
||||||
|
|
||||||
- **English (en)** - Base language
|
|
||||||
- **Spanish - Mexico (es-MX)**
|
|
||||||
- **French - Canada (fr-CA)**
|
|
||||||
|
|
||||||
## How String Catalogs Work
|
## How String Catalogs Work
|
||||||
|
|
||||||
|
|||||||
@ -261,7 +261,9 @@ let result = largeArray
|
|||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
### Prefer Typed Throws (Swift 6)
|
### Prefer Typed Throws (Swift 6 / Xcode 16+)
|
||||||
|
|
||||||
|
Typed throws requires the **Swift 6 compiler** (shipped with Xcode 16). The feature works at any deployment target, but your project must compile with Swift 6. If your team hasn't migrated yet, continue using untyped `throws`.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
// Swift 6 - Typed throws
|
// Swift 6 - Typed throws
|
||||||
|
|||||||
@ -45,10 +45,7 @@ Each protocol should represent one capability (Interface Segregation Principle):
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
// GOOD - Focused protocols
|
// GOOD - Focused protocols
|
||||||
protocol Identifiable {
|
// Note: Swift provides Identifiable already — adopt it, don't redefine it.
|
||||||
var id: UUID { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol Nameable {
|
protocol Nameable {
|
||||||
var displayName: String { get }
|
var displayName: String { get }
|
||||||
}
|
}
|
||||||
@ -58,8 +55,13 @@ protocol Timestamped {
|
|||||||
var updatedAt: Date { get }
|
var updatedAt: Date { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compose as needed
|
// Compose as needed (Identifiable comes from Swift standard library)
|
||||||
struct User: Identifiable, Nameable, Timestamped { ... }
|
struct User: Identifiable, Nameable, Timestamped {
|
||||||
|
let id: UUID
|
||||||
|
var displayName: String
|
||||||
|
var createdAt: Date
|
||||||
|
var updatedAt: Date
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
|||||||
@ -99,34 +99,6 @@ final class NavigationStore {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tab View
|
|
||||||
|
|
||||||
### Use Tab API Not tabItem()
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// BAD - Old tabItem pattern
|
|
||||||
TabView {
|
|
||||||
HomeView()
|
|
||||||
.tabItem {
|
|
||||||
Label("Home", systemImage: "house")
|
|
||||||
}
|
|
||||||
SettingsView()
|
|
||||||
.tabItem {
|
|
||||||
Label("Settings", systemImage: "gear")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOOD - Tab API (iOS 18+)
|
|
||||||
TabView {
|
|
||||||
Tab("Home", systemImage: "house") {
|
|
||||||
HomeView()
|
|
||||||
}
|
|
||||||
Tab("Settings", systemImage: "gear") {
|
|
||||||
SettingsView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Observable Pattern
|
## Observable Pattern
|
||||||
|
|
||||||
### Use @Observable Not ObservableObject
|
### Use @Observable Not ObservableObject
|
||||||
@ -294,24 +266,32 @@ func makeView(for type: ViewType) -> some View {
|
|||||||
|
|
||||||
## Lists and ForEach
|
## Lists and ForEach
|
||||||
|
|
||||||
### Don't Convert to Array for Enumeration
|
### Use Identifiable Conformance for ForEach
|
||||||
|
|
||||||
|
Let `ForEach` use `Identifiable` conformance directly — don't bypass it with manual key paths or offset-based identity.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
// BAD - Unnecessary Array conversion
|
// BAD - offset-based id breaks animations when items change
|
||||||
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
|
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
// GOOD - Use indices or zip
|
// BAD - index-based id has the same animation problem
|
||||||
ForEach(items.indices, id: \.self) { index in
|
ForEach(items.indices, id: \.self) { index in
|
||||||
let item = items[index]
|
let item = items[index]
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
// GOOD - If you need both
|
// GOOD - Identifiable items work directly (protocol-based)
|
||||||
ForEach(Array(zip(items.indices, items)), id: \.0) { index, item in
|
ForEach(items) { item in
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GOOD - When index is also needed, use enumerated keyed on the item's identity
|
||||||
|
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
|
||||||
|
// Stable identity comes from item.id (Identifiable conformance),
|
||||||
|
// not from the offset — so animations and diffing work correctly.
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hide Scroll Indicators
|
### Hide Scroll Indicators
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user