Republished
Republished copied to clipboard
⏩ `Republished` is a property wrapper that enables nesting SwiftUI ObservableObjects.
Republished ⏩
The @Republished
proprty wrapper allows an ObservableObject
nested
within another ObservableObject
to naturally notify SwiftUI of changes.
What it achieves
Some architectural patterns split view logic, from presentation logic, from domain logic. Often with this separation of concerns the developer ends up having to set up manual data bindings between the ViewModel (or Presenter) and the underlying DomainModel (or business logic). 'Reactivity' is hard — and tools used to handle it directly, like RxSwift and Combine, are error prone.
With @Republished
you can lean into SwiftUI's regular change observation pipeline and code imperatively — while still keeping your model abstraction layer crisp for your business essential logic.
struct MyView: View {
@StateObject var viewModel: MyViewModel
var body: some View {
Text(viewModel.myTitle)
}
}
final class MyViewModel: ObservableObject {
/// Without Republished SwiftUI would be unaware of changes.
@Republished var domainModel: MyDomainModel
var myTitle: String {
"\(domainModel.beerBottles.count) bottles of beer on the wall!"
}
}
final class MyDomainModel: ObservableObject {
/// Essential business logic is kept clean and testable.
@Published var beerBottles: BeerBottle
func takeOneDown() {
beerBottles.removeLast()
}
}
How it works
Nested ObservableObjects
don't play well with SwiftUI.
An ObservableObject
is an identity type, not a value type. This means a field on an outer ObservableObject
containing an inner ObservableObject
doesn't change when the inner object changes. This means SwiftUI isn't notified of any changes happening and won't update corresponding views.
This package solves this problem by subscibing to the inner object and republishing its change notifications.
How to get started
Swift Package Manager
Use this library via Swift Package Manger by adding a dependency in your Package.swift.
.package(url: "https://github.com/adam-zethraeus/Republished", from: "1.1.1")
Usage
The outer ObservableObject
should hold the inner one in a var annotated
with the @Republished
property wrapper.
@Republished private var inner: InnerObservableObject
The inner ObservableObject's
objectWillChange
notifications will be
re-emitted by the outer ObservableObject
, allowing it to provide SwiftUI
compatible accessors derived from the inner.
final class OuterObservableObject: ObservableObject {
/* ... */
@Republished private var inner: InnerObservableObject
var infoFromInner: String { "\(inner.info)" }
}
Additional API Details
You can also use @Republished
with an optional ObservableObject
or an
array of ObservableObjects
.
(Note that regular @StateObject
and @ObservedObject
do not allow this.)
final class OuterObservableObject: ObservableObject {
@Republished private var inner: InnerObservableObject
@Republished private var optionalInner: InnerObservableObject?
@Republished private var innerList: [InnerObservableObject]
}
Note
The outer ObservableObject
will only republish notifications from accessed fields.
If an inner ObservableObject is never touched it will not be subscribed to. (It also
can't be affecting a view if it's not read.)
Working Examples
The RepublishedExampleApp
contains an example of an inner ObservableObject
domain model, used by an outer ObservableObject
view model in a regular SwiftUI View
.
It also shows how to use @Republished
with optionals and arrays of inner observable objects.
Credits
This library was inspired by some of the challenges described in a blog post by @mergesort introducing their the data store library Boutique
.