Swift Usage
Use stores in iOS apps with SwiftUI or UIKit.
Android
Android support is coming soon.
Setup
Initialize the store in your app's entry point:
import Brownie
import ReactBrownfield
import SwiftUI
let initialState = BrownfieldStore(
counter: 0,
user: User(name: "")
)
@main
struct MyApp: App {
init() {
// Start React Native
ReactNativeBrownfield.shared.startReactNative {
print("React Native loaded")
}
// Register store with initial state
BrownfieldStore.register(initialState)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
SwiftUI
@UseStore Property Wrapper
The @UseStore property wrapper provides reactive access to a selected slice of state using KeyPath selectors. This ensures your view only re-renders when the selected value changes.
import Brownie
import SwiftUI
struct CounterView: View {
@UseStore(\BrownfieldStore.counter) var counter
var body: some View {
VStack {
Text("Count: \(Int(counter))")
Button("Increment") {
$counter.set { $0 + 1 }
}
}
}
}
Selectors
Every @UseStore requires a KeyPath selector. This:
- Forces explicit state selection
- Prevents unnecessary re-renders (only updates when selected value changes)
- Provides type-safe access to state
// Select primitive
@UseStore(\BrownfieldStore.counter) var counter // counter is Double
// Select nested object
@UseStore(\BrownfieldStore.user) var user // user is User
Equatable Requirement
Selected values must conform to Equatable for change detection.
Updating State
The projected value ($) returns a standard SwiftUI Binding<Value>:
// Use with any SwiftUI control that accepts Binding
Stepper(value: $counter) { Text("Count: \(Int(counter))") }
Slider(value: $counter, in: 0...100)
Toggle("Enabled", isOn: $isEnabled)
// Set with closure (Brownie extension on Binding)
$counter.set { $0 + 1 }
// Access nested properties via Binding subscript
TextField("Name", text: $user.name)
Multiple Selectors
Use multiple @UseStore declarations for different state slices. Each only triggers re-renders when its selected value changes:
struct MyView: View {
@UseStore(\BrownfieldStore.counter) var counter
@UseStore(\BrownfieldStore.user) var user
var body: some View {
VStack {
Text("Count: \(Int(counter))")
Text("User: \(user.name)")
Button("Increment") {
$counter.set { $0 + 1 }
}
}
}
}
TextField Binding
Use the binding directly or select a nested property:
// Option 1: Select the nested property directly
struct UserView: View {
@UseStore(\BrownfieldStore.user.name) var name
var body: some View {
TextField("Name", text: $name)
.textFieldStyle(.roundedBorder)
}
}
// Option 2: Select parent and access nested binding
struct UserView: View {
@UseStore(\BrownfieldStore.user) var user
var body: some View {
TextField("Name", text: $user.name)
.textFieldStyle(.roundedBorder)
}
}
UIKit
For UIKit, use StoreManager to retrieve stores and subscribe for updates.
Full UIKit Example
import UIKit
import Brownie
class CounterViewController: UIViewController {
private var store: Store<BrownfieldStore>?
private var cancelSubscription: (() -> Void)?
private let counterLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 24, weight: .bold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let incrementButton: UIButton = {
var config = UIButton.Configuration.borderedProminent()
config.title = "Increment"
let button = UIButton(configuration: config)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupStore()
}
private func setupUI() {
view.backgroundColor = .systemBackground
let stack = UIStackView(arrangedSubviews: [counterLabel, incrementButton])
stack.axis = .vertical
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
incrementButton.addTarget(self, action: #selector(incrementTapped), for: .touchUpInside)
}
private func setupStore() {
// Retrieve store from manager
store = StoreManager.get(key: BrownfieldStore.storeName, as: BrownfieldStore.self)
guard let store else {
counterLabel.text = "Store not found"
return
}
// Initial UI update
updateUI(with: store.state)
// Subscribe to changes
cancelSubscription = store.subscribe { [weak self] state in
self?.updateUI(with: state)
}
}
private func updateUI(with state: BrownfieldStore) {
counterLabel.text = "Count: \(Int(state.counter))"
}
@objc private func incrementTapped() {
store?.set { $0.counter += 1 }
}
deinit {
cancelSubscription?()
}
}
Subscribe to Specific Property
Subscribe to a specific property to only receive updates when it changes:
// Subscribe to counter only
cancelSubscription = store.subscribe(\.counter) { [weak self] counter in
self?.counterLabel.text = "Count: \(Int(counter))"
}
API Reference
@UseStore
Property wrapper for SwiftUI with required KeyPath selector:
@UseStore(\BrownfieldStore.counter) var counter
Binding Extension
Brownie adds a set method to Binding for closure-based updates:
$counter.set { $0 + 1 } // increment using current value
Store<State>
StoreManager
BrownieStoreProtocol