Swiftpack.co - Swift Packages by wayfair

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.

Packages published by wayfair

wayfair/vsm-ios v1.1.3
An iOS framework for the VSM Architecture
⭐️ 7
🕓 9 weeks ago
🔖 Release Notes

Releases

The markdown parsing is broken/disabled for release notes. Sorry about that, I'm chasing the source of a crash that's been bringing this website down for the last couple of days.
v1.1.3
9 weeks ago
## Overview Added support for Vision OS ## What's Changed * Vision os support by @bdunay3 in https://github.com/wayfair/vsm-ios/pull/43 **Full Changelog**: https://github.com/wayfair/vsm-ios/compare/v1.1.2...v1.1.3
v1.1.2
19 weeks ago
## Overview This release adds a couple of quality-of-life improvements to VSM ### Observing Generic Publishers You can now observe generic publishers without having to erase them with `.eraseToAnyPublisher()`, where applicable. Old: ```swift struct LoaderModel { func load() -> AnyPublisher<MyViewState, Never> { Just(.loading) .merge(with: webRequestPublisher()) .eraseToAnyPublisher() } ... } ``` New: ```swift struct LoaderModel { func load() -> some Publisher<MyViewState, Never> { Just(.loading) .merge(with: webRequestPublisher()) } ... } ``` ### KeyPath Support in MutatingCopyable If you need to change a single value on a model's mutable property, you can now use a new overload on `MutatingCopyable` (thanks to @bdunay3) that allows you to mutate by key path. This enables some interesting possibilities for working with key paths. At a minimum, it provides a more straight-forward spelling for changing a single value on a struct's mutable property in a single line of code. New: ```swift struct LoadedModel { ... var isSaving: Bool = false func save(newValue: String) -> some Publisher<MyViewState, Never> { Just(.loaded(self.copy(mutatingPath: \.isSaving, value: true))) .merge(with: savePublisher(forValue: newValue)) } ... } ``` ## What's Changed * Maintenance by @albertbori in https://github.com/wayfair/vsm-ios/pull/38 * Observe Some (Generic) Publisher by @albertbori in https://github.com/wayfair/vsm-ios/pull/39 * Update sersoft-gmbh/xcodebuild-action action to v3 by @renovate in https://github.com/wayfair/vsm-ios/pull/40 * Mutatingcopyable keypath by @bdunay3 in https://github.com/wayfair/vsm-ios/pull/41 **Full Changelog**: https://github.com/wayfair/vsm-ios/compare/v1.1.1...v1.1.2
VSM 1.1.1 Release
39 weeks ago
This release adds support to use VSM on watchOS and tvOS.
VSM v1.1 Release Notes
1 year ago
VSM v1.1 contains several bug fixes and improvements to the framework with one minor (rename only) breaking change. ## New Features ### Event-Specific State Publishers This release introduces a new, preferred way of observing state changes for miscellaneous view-logic (animations, etc.) through event-specific state publishers. The `willSetPublisher` and `didSetPublisher` replace the (deprecated) `publisher` property on the `StateContainer` object and the `@ViewState` and `@RenderedViewState` property wrappers. These publishers publish state changes in the `willSet` and `didSet` view state events, respectively. This solves an issue with excessive `body` calls in SwiftUI and provides more flexibility to both SwiftUI and UIKit views for working with and comparing a view's view state. (#31) Example Usage ```swift // SwiftUI struct MyView: View { @ViewState var state: MyViewState @State var progress: Double = 0 var body: some View { ProgressView("Loading...", value: progress) .onAppear { if case .initialized(let loaderModel) = state { $state.observe(loaderModel.load()) } } .onReceive($state.willSetPublisher) { newState in switch (state, newState) { case (.loading(let oldLoadingModel), .loading(let newLoadingModel)): guard oldLoadingModel.loadedBytes < newLoadingModel.loadedBytes else { return } print(">>> Animating progress from \(oldLoadingModel.loadedBytes) to \(newLoadingModel.loadedBytes) bytes") withAnimation() { progress = newLoadingModel.loadedBytes / newLoadingModel.totalBytes } default: break } } } } ``` ### State Comparison in RenderedViewState The `@RenderedViewState` property wrapper can now be initialized with alternative render function signature that allows engineers to compare the current and future state just before the new state is set. As of this release the two acceptable render function signatures are: `render()` and the new `render(_ newState: SomeViewState)`. This allows engineers to perform any necessary view-logic where the current and future view states need to be compared. (Animations, conditional view updates, etc.) This new option will increase both the performance and accuracy of VSM views in UIKit. (#32) Example Usage ```swift // UIKit class MyViewController: UIViewController { @RenderedViewState var state: MyViewState ... init() { _state = .init(wrappedValue: .initialized(LoaderModel()), render: Self.render) } ... func render(_ newState: MyViewState) { if state.saveProgress < newState.saveProgress) { animateSaveProgress(from: state.saveProgress, to: newState.saveProgress) } } } ``` ### Manual Rendering Kickoff When using the `@RenderedViewState` property wrapper, any access to the wrapped value (aka the view state) after initialization will trigger the automatic view state rendering to begin. There are situations where this is insufficient. To provide a solution for alternative cases where automatic rendering needs an explicit kickoff point, a new function was added to the property wrapper's projected value that will begin rendering the view state without the need to access the view state property. This new function is called like so: `$state.startRendering(on: self)`. It can be invoked in any UIKit view lifecycle event (or equivalent). (#29) Example Usage ```swift // UIKit class MyViewController: UIViewController { @RenderedViewState var state: MyViewState ... init() { _state = .init(wrappedValue: .initialized(LoaderModel()), render: Self.render) } ... func viewDidLoad() { super.viewDidLoad() $state.startRendering(on: self) } ... } ``` ## Bug Fixes _None_ ## Documentation Updates - The documentation, guides, and examples have been updated to promote the new features above. (#34) ## Breaking Changes - In the last release, the `@autoclosure` parameter decorator was accidentally omitted from a function signature for observing a debounced action. This has been corrected in this release, but it may require callers to add function call indicators to invocation points. e.g. `$state.observe(state.foo, debounced: 0.5)` -> `$state.observe(state.foo(), debounced: 0.5)`. This is a spelling-only breakage and has no impact on the functionality. (#33 | [PR Comment](https://github.com/wayfair/vsm-ios/pull/33/files#r1120444265)) ## Internal Framework Changes - The demo Shopping app has been fully converted to use these new features and best practices. (#34) - Unit tests were reorganized by public API and expanded to test every concrete implementation of public APIs. These concrete implementations are `StateContainer`, `ViewState`, and `RenderedViewState`. (#33) - CI tasks will now build asynchronously, allowing for faster build times. (#21) ## Migration Instructions ### All Frameworks > Note: The `$state.publisher` property has been deprecated. It will be removed in a future version of the VSM framework. Its usage will produce compiler warnings. 1. Resolve any compiler errors resulting from the [breaking changes](#breaking-changes). ### SwiftUI 1. Replace any uses of `$state.publisher` with `$state.willSetPublisher`. 1. Test features to detect and fix any view-logic regressions. ### UIKit 1. Replace any uses of `$state.publisher` with either `$state.willSetPublisher` or `$state.didSetPublisher`, depending on the requirement. 1. (Optional) Implement the new `render(_ newState: SomeViewState)` in place of other mechanisms where new and old state were needing to be compared to support special view-logic while rendering.
VSM v1 Release Notes
1 year ago
VSM has reached v1.0 after 8 months of use in high-traffic production code, several solidifying bug fixes, and incremental improvements to the ergonomics of the framework. ## New Features ### VSM Property Wrappers This release introduces a new, preferred way of working with VSM through property wrappers. ([Proposal](https://github.com/wayfair/vsm-ios/blob/main/ADRs/2022-12-08-PropertyWrappers-ADR.md)) Example Usage of `@ViewState` (SwiftUI only) ```swift // SwiftUI struct MyView: View { @ViewState var state: MyViewState = .initialized(LoaderModel()) var body: some View { switch state { case .initialized(let loaderModel): ProgressView() .onAppear { $state.observe(loaderModel.load()) } case .loading: ProgressView() case .error(let errorModel): Text(errorModel.message) Button("Retry") { $state.observe(errorModel.retry()) } case .loaded(let contentModel): Text(contentModel.details) } } } ``` Example Usage of `@RenderedViewState` (UIKit only) ```swift // UIKit class MyViewController: UIViewController { // view properties @RenderedViewState(MyViewController.render) var state: MyViewState = .initialized(LoaderModel()) func viewDidLoad() { super.viewDidLoad() if case .initialized(let loaderModel) = state { $state.observe(loaderModel.load()) } let action = UIAction() { [weak self] action in guard let strongSelf = self else { return } guard case .error(let errorModel) = strongSelf.state else { return } $state.observe(errorModel.retry()) } errorButton.addAction(action, for: .touchUpInside) } func render() { switch state { case .initialized, .loading: loadingIndicator.isHidden = false errorView.isHidden = true contentView.isHidden = true case .error(let errorModel): loadingIndicator.isHidden = true errorView.isHidden = false errorMessage.text = errorModel.message contentView.isHidden = true case .loaded(let contentModel): loadingIndicator.isHidden = true errorView.isHidden = true contentView.isHidden = false contentLabel.text = contentModel.details } } } ``` ### StateContainer Publisher The `StateContainer` type now provides a publisher of the current `State`. You can access it via `$state.publisher` when using view state property wrappers or via `statContainer.publisher` if using a StateContainer directly. This new publisher will emit new state values on `didSet`, as opposed to `stateContainer.$state` which emits values on `willSet`. ### Debug Functions The debug logging functionality has been updated and can be configured to output different formats and events. You can access it statically for all state containers or individually per state container. Static Usage ```swift // prints the state changes for all VSM views in the app ViewState._debug(options: [...]) // or RenderedViewState._debug(options: [...]) // or StateContainer._debug(options: [...]) ``` Local Usage ```swift // prints the current state and future state updates for this VSM view $state._debug(options: [...]) // or stateContainer._debug(options: [...]) ``` ### Invalid State Access The new SwiftUI property wrapper helps avoid certain data operation and state pitfalls. In previous versions, the VSM framework allowed engineers to perform data operations within SwiftUI initializers like so: ```swift init() { let loaderModel = LoaderModel() _container = .init(state: .initialized(loaderModel)) container.observe(loaderModel.load()) } ``` This anti-pattern most often results in undesired behavior or data operations because SwiftUI View initializers can be called any number of times. The new `@ViewState` property wrapper will now emit a runtime warning if you attempt to observe the state from within the initializer. ```swift init() { let loaderModel = LoaderModel() _state_ = .init(state: .initialized(loaderModel)) $state.observe(loaderModel.load()) // runtime warning about accessing the value of a state object before it is assigned to a view } ``` This encourages engineers to use the `onAppear` view modifier instead, which is the recommended approach for SwiftUI: ```swift ProgressView() .onAppear { if case .initialized(let loaderModel) = state { $state.observe(loaderModel.load()) } } ``` > Note: In SwiftUI, `onAppear` is guaranteed to execute before the first frame is drawn. Any synchronous changes to `@ViewState`, `@State`, `@ObservedObject`, or `@StateObject` properties will cause the view's `body` to be reevaluated before the first frame is drawn. ## Bug Fixes - **Extra Frame Render/Flicker** - Fixed some cases where an initialization state would accidentally render 1 frame before moving to the next state (ie, a loading state) when observing an action that returns a publisher which emits a value on the main thread. The state change will now happen synchronously on the main thread. - **Async Closure Confusion** - Fixed an issue (or created a workaround for) where the Swift runtime would confuse synchronous closures as asynchronous closures. This was especially evident in protocol extensions. This was done by adding "Async" to the function names that have async closure parameters. ## Documentation Updates - The documentation, guides, and examples have been updated to promote the new view state property wrappers instead of StateContainer and ViewStateRendering. ## Breaking Changes - Renamed all asynchronous observe functions from `observe({ await foo() })` to `observeAsync({ await foo() })` - (Uncommon) Any functionality requiring state publisher observation to be queued asynchronously on the main thread will no longer work implicitly. The `observe(...)` function now favors staying on the main thread where possible. If you need main thread async-execution, then you must use `.subscribe(on: DispatchQueue.main)` explicitly within your model actions. - (Uncommon) Some `observe(...)` function overloads no longer accept closures or function types as parameters. ## Internal Framework Changes - The demo Shopping app has been fully converted to use these new best practices. - UIKit VSM examples have been added to the demo Shopping app. - UI tests have been added for the entire demo Shopping app to prevent UI regressions in any future framework updates. - Unit tests have been added to `observe(somePublisher)` to ensure main-thread execution for synchronous execution paths. - Files, folders, and types have been reorganized ## Migration Instructions ### All Frameworks 1. Resolve any compiler errors resulting from the [breaking changes](#breaking-changes). 1. (Uncommon) Test features that explicitly rely on async-main thread progression to ensure their behaviors are unchanged. 1. Remove the `ViewStateRendering` conformance from your Views, UIViews, or UIViewControllers. > Note: The `ViewStateRendering` protocol has been deprecated. It will be removed in a future version of the VSM framework. Its usage will produce compiler warnings. ### SwiftUI 1. In each view, replace `@StateObject var container: StateContainer<FooViewState>` with `@ViewState var state: FooViewState`. 1. Change container initialization to initialize the state property instead. 1. Replace any references to `container.state` with `state`. 1. Replace any references to `observe(...)` or `container.observe(...)` with `$state.observe(...)`. 1. (Uncommon) Move any data observation operations from the view's initializer to the `onAppear` closure of an appropriate subview. ### UIKit 1. In each view, replace `var container: StateContainer<FooViewState>` with either: - `@RenderedViewState var state: FooViewState` - `@RenderedViewState(MyViewController.render) var state: FooViewState = .fooState(BarModel())` 1. Change container initialization to initialize the state property instead. 1. Replace any references to `container.state` with `state`. 1. Replace any references to `observe(...)` or `container.observe(...)` with `$state.observe(...)`. 1. Add or change your rendering function name and signature to `func render() { ... }`. 1. Remove any state subscription code (i.e. `stateSubscription = container.$state.sink { ... }`)
Initial Release
1 year ago
Contains the minimum viable implementation of VSM for iOS: - Support for SwiftUI and UIKit - `ViewStateRendering` aids pattern adherence for Views - `StateContainer` for managing observation of current and future states - Convenient overloads that allow for flexible State/Model design and minimal boilerplate
iOS macOS watchOS tvOS

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics