A simple Elm-like Store for SwiftUI, based on ObservableObject

Related tags

UI ObservableStore
Overview

ObservableStore

A simple Elm-like Store for SwiftUI, based on ObservableObject.

Like Elm or Redux, ObservableStore.Store offers reliable unidirectional state and effects management. All state updates happen through actions passed to an update function. This guarantees your application will produce exactly the same state, given the same actions in the same order.

Because Store is an ObservableObject, it can be used anywhere in SwiftUI that ObservableObject would be used.

Store is meant to be used as part of a single app-wide, or major-view-wide component. It deliberately does not solve for nested components or nested stores. Following Elm, deeply nested components are avoided. Instead, it is designed for apps that use a single store, or perhaps one store per major view. Instead of decomposing an app into many stateful components, ObservableStore favors decomposing an app into many stateless views that share the same store and actions. Sub-views can be passed data through bare properties of store.state, or bindings, which can be created with store.binding, or share the store globally, through EnvironmentObject. See https://guide.elm-lang.org/architecture/ and https://guide.elm-lang.org/webapps/structure.html for more about this philosophy.

Example

A minimal example of Store used to increment a count with a button.

import SwiftUI
import os
import Combine
import ObservableStore

/// Actions
enum AppAction {
    case increment
}

/// Services like API methods go here
struct AppEnvironment {
}

/// App state
struct AppState: Equatable {
    var count = 0

    /// State update function
    static func update(
        state: AppState,
        environment: AppEnvironment,
        action: AppAction
    ) -> Update
    {
        
   switch action {
        
   case .
   increment
   :
            
   var model 
   = state
            model.
   count 
   = model.
   count 
   + 
   1
            
   return 
   Update(
   state: model)
        }
    }
}


   struct 
   AppView: 
   View {
    
   @StateObject 
   var store 
   = 
   Store(
        
   update: AppState.
   update,
        
   state: 
   AppState(),
        
   environment: 
   AppEnvironment()
    )

    
   var body: 
   some View {
        VStack {
            
   Text(
   "The count is: \(store.state.count)")
            
   Button(
                
   action: {
                    
   // Send `.increment` action to store,

                       
   // updating state.

                       store.
   send(
   action: .
   increment)
                },
                
   label: {
                    
   Text(
   "Increment")
                }
            )
        }
    }
}
  

Store, state, updates, and actions

A Store is a source of truth for a state. It's an ObservableObject. You can use it in a view via @ObservedObject or @StateObject to power view rendering.

Store exposes a single @Published property, state, which represents your application state. All updates and effects to this state happen through actions sent to store.send.

state is read-only, and cannot be updated directly. Instead, like Elm, or Redux, all state changes happen through a single update function, with the signature:

(State, Environment, Action) -> Update
   

   

The Update returned is a small struct that contains a new state, plus any effects this state change should generate (more about that in a bit).

state is modeled as an Equatable type, typically a struct. Updates only mutate the state property on store when they are not equal. This means returning the same state twice is a no-op, and SwiftUI view body recalculations are only triggered if the state actually changes. Since state is Equatable, you can also make Store-based views EquatableViews, wherever appropriate.

Effects

Updates are also able to produce asyncronous effects via Combine publishers. This lets you schedule asyncronous things like HTTP requests, or database calls, in response to actions. Using effects, you can model everything via a deterministic sequence of actions, even asyncronous side-effects.

Effects are modeled as Combine Publishers which publish actions and never fail.

For convenience, ObservableStore defines a typealias for effect publishers:

public typealias Fx<Action> = AnyPublisher
   Never>
  

The most common way to produce effects is by exposing methods on Environment that produce effects publishers. For example, an asyncronous call to an authentication API service might be implemented in Environment, where an effects publisher is used to signal whether authentication was successful.

struct Environment {
    // ...
    func authenticate(credentials: Credentials) -> AnyPublisher
   Never> {
      
   // ...

       }
}
  

The update function can pass this effect through Update(state:fx:)

func update(
    state: State,
    environment: Environment,
    action: Action
) -> Update
    {
    
   switch action {
    
   // ...

       
   case .
   authenticate(
   let credentials)
   :
        
   return 
   Update(
            
   state: state,
            
   fx: environment.
   authenticate(
   credentials: credentials)
        )
    }
}
  

Store will manage the lifecycle of any publishers passed through fx this way, piping the actions they produce back into the store, producing new states.

Comments
  • Synthesize `update(state:actions:environment:)` for ModelProtocol

    Synthesize `update(state:actions:environment:)` for ModelProtocol

    #15 caused fx to be run immediately instead of joined on main. The intent was to allow for composing multiple actions by sending up many Just(.action) fx. However,

    • This is verbose to write, and rather "chatty".
    • It also makes the store implementation less straightforward, since without joining on main, we must check if fx was completed immediately before adding to fx dictionary. Joining on main solves this problem by running the fx on next tick, after the fx has been added to dictionary.
    • Additionally, it means that off-main-thread fx are required to be joined manually on main to prevent SwiftUI from complaining.

    This PR introduces an alternative approach to composing multiple update functions. Any type that conforms to ModelProtocol has a update(state:actions:environment) static function synthesized for it. This function can be used to simulate the effect of sending multiple actions in sequence immediately, in effect, composing the actions.

    update(
        state: state,
        actions: [
            .setEditor(
                text: detail.entry.body,
                saveState: detail.saveState
            ),
            .presentDetail(true),
            .requestEditorFocus(false)
        ],
        environment: environment
    )
    

    State is updated immediately, fx are merged, and last transaction wins.

    Now that we have a way to immediately sequence actions in same state update, we no longer need to run fx on same tick. Joining on main is my preference from an API perspective because it has fewer footguns in implementation and use.

    Breaking changes

    • Remove Update.pipe. Redundant now. Was never happy with it anyway. It was an inelegant way to accomplish the same thing as update(state:actions:environment:).
    • Revert fx to be joined on main thread. We join on main with a .default QoS, because fx should be async/never block user interaction.
    opened by gordonbrander 0
  • Introduce ViewStore for scoped component stores

    Introduce ViewStore for scoped component stores

    This PR introduces the ability to create scoped stores for sub-components, called ViewStores. ViewStores are conceptually like bindings for stores, except that they expose the store API, instead of a binding API. This approach is inspired by the component mapping pattern from Elm.

    We also update the implementation of Store so that fx are run immediately instead of being joined on main queue. This is to avoid adding delay when intercepting child actions and then sending them back up as fx.

    Changes

    • Introduce ModelProtocol, which implements an update(state:action:environment) static function.
      • Model protocol allows us to treat action and environment as associatedtypes, which simplifies many of our other type signatures.
    • Introduce StoreProtocol, a protocol that describes a kind of store.
    • Introduce ViewStore<Model: ModelProtocol>, which implements StoreProtocol
    • Implement StoreProtocol for Store
    • Introduce CursorProtocol, which describes how to map from one domain to another, and provides a convenience function for updating child components.
    • Replace .binding with generic Binding intializer for any StoreProtocol
    • Add new tests

    Breaking changes

    • Fx are now run immediately, meaning you will have to manually join asyncronous fx to main queue with .receive(on: DispatchQueue.main)
    • Store requires that state implement ModelProtocol. This allows us to simplify the signatures of many other APIs
      • Update type signature changes from Update<State, Action> to Update<Model: ModelProtocol>.
      • Store type signature changes from Store<State, Action, Environment> to Store<Model: ModelProtocol>
      • Store initializer changes from Store.init(update:state:environment:) to Store.init(state:environment:). Now that state conforms to ModelProtocol, you don't have to explicitly pass in the update function as a closure. We can just call the protocol implementation.
    • store.binding has been removed in favor of Binding(store:get:send:)
    opened by gordonbrander 0
  • Reorder update fn signature to State, Action, Environment

    Reorder update fn signature to State, Action, Environment

    This PR changes the order of update function arguments:

    • Changes update function signature from update(State, Environment, Action) to update(State, Action, Environment)
    • Changes Store from Store<State, Environment, Action> to Store<State, Action, Environment>

    Why?

    • This order of arguments is used by two other Elm-like libraries: Swift Composable Architecture and Redux-like state container.
    • Context arguments like environment often seem to be passed last in Swift. E.g. UIViewRepresentable.Context is passed last to methods in SwiftUI.
    • State and Action are always used, but Environment might be nil, or an empty struct. You don't always need environment. It is optional.
    • The order reads better in the signature, since state and action are always paired together in update function and Update struct.
    opened by gordonbrander 0
  • Create binding form that does not set animation

    Create binding form that does not set animation

    Previously if no animation was passed to binding, we would send withAnimation(nil). However, we don't want to set withAnimation(nil) unless explicitly asked, as this may override withAnimation called elsewhere.

    Instead, we now introduce two forms of Store.binding:

    Store.binding(get:tag:)
    Store.binding(get:tag:animation:)
    

    The first form does not call withAnimation, leaving the transaction state alone.

    opened by gordonbrander 0
  • Update arg labels for send, subscribe

    Update arg labels for send, subscribe

    Fixes #7

    Per https://www.swift.org/documentation/api-design-guidelines/#argument-labels,

    If the first argument forms part of a grammatical phrase, omit its label.

    This is the case for store.send(_), so we remove the label.

    When the first argument forms part of a prepositional phrase, give it an argument label.

    We subscribe to a publisher, therefore store.subscribe(to:)

    opened by gordonbrander 0
  • Introduce `mergeFx`

    Introduce `mergeFx`

    Fixes #8.

    This PR also removes Update.transaction(_) since name conflicts with Update.transaction property. Note I decided against adding fx for the same reason. I will avoid adding plain old setters for now, since chaining sets is not really the most common need.

    If we find we really need more than property setters for simple sets in the future, we may want to add a map function instead.

    opened by gordonbrander 0
  • Deprecate send(action:) in favor of send(_)

    Deprecate send(action:) in favor of send(_)

    The action label is redundant, and unnecessary vis Swift's own style guide https://www.swift.org/documentation/api-design-guidelines/#argument-labels.

    Consider removing.

    enhancement 
    opened by gordonbrander 0
  • Receive Fx on main thread

    Receive Fx on main thread

    Receive Fx on main thread. This does two important things:

    First, SwiftUI requires that any state mutations that would change views happen on the main thread. Receiving on main ensures that all fx-driven state transitions happen on main, even if the publisher is off-main-thread.

    Second, if we don’t schedule to receive on main, it is possible for publishers to complete immediately, causing receiveCompletion to attempt to remove the publisher from cancellables before it is added. This was happening with Empty() publishers returned by default when you do not specify Update(fx:).

    By scheduling to receive publisher on main, we force publisher to complete on next tick, ensuring that it is always first added, then removed from cancellables.

    This is an alternative to the flag-based approach in https://github.com/gordonbrander/ObservableStore/blob/2022-03-12-fx-tests-2/Sources/ObservableStore/ObservableStore.swift#L175.

    More background

    Cancellables will cancel themselves they are released (makes sense). This means you must keep cancellables alive for the duration of the lifetime of the publisher by storing them somewhere. In our case, we store them in a map of [UUID: Cancellable].

    So anyway, we remove them when publisher completes in the receiveCompletion. HOWEVER, it is possible for a publisher to IMMEDIATELY complete during the same tick. This causes the receiveCompletion code which removes the cancellable from the map to run BEFORE the cancellable is added to the map. The result is that these immediately-completing cancellables leak, in the sense that they build up and are never removed. Cancellables are very tiny, so we didn't notice the impact on memory, but it is bad hygiene.

    opened by gordonbrander 0
  • Allow specifying transaction for Update

    Allow specifying transaction for Update

    Allow updates to specify transaction under which state update should take place.

    This allows update functions to drive explicit animations. Animations, as well as state changes, can be specified.

    opened by gordonbrander 0
  • receiveValue with weak self closure

    receiveValue with weak self closure

    This allows store to be released before effect is complete, without creating a retain cycle. Ordinarily store's lifetime is the lifetime of the app, so this should not be an issue for single-store apps, but a weak reference to self should used here regardless. Additionally, this will now avoid a possible retain cycle if using multiple short-lived stores, such as one per component.

    opened by gordonbrander 0
  • Introduce KeyedCursorProtocol, remove ViewStore in favor of forward

    Introduce KeyedCursorProtocol, remove ViewStore in favor of forward

    This PR sketches out one potential solution to #18. It refactors our approach to sub-components by decomplecting action sending from state getting.

    • Removes ViewStore
    • Introduces Address.forward(send:tag:) which gives us an easy way to create tagged send functions. This solves one part of what ViewStore was solving.
    • Introduces Binding(state:send:tag:) which gives us the binding equivalent to Address.forward
    • Introduces KeyedCursorProtocol which offers an alternative cursor for subcomponents that need to be looked up within dynamic lists.

    This refactor is in response to the awkwardness of the ViewStore/Cursor paradigm for components that are part of a dynamic list. Even if we had created a keyed cursor initializer for ViewStore, it necessarily would have had to hold an optional (nillable) state. This is because ViewStore lookup was dynamic, and this trips up the lifetime typechecking around the model. In practice, a view would not exist if its model did not exist, but this is not a typesafe guarantee for dynamic list lookups.

    Anyway, the whole paradigm of looking up child from parent dynamically is a bit odd for list items. In SwiftUI the typical approach is to ForEach, and then pass the model data down as a static property to the view. This guarantees type safety, since a view holds its own copy of the data. What if we could do something more like that?

    The approach in this PR leans into this approach. State can be passed to sub-components as plain old properties. Address.forward can be used to create view-local send functions that you can pass down to sub-views. Binding gets a similar form. In both cases, we can use a closure to capture additional parent-scoped state, such as an ID for lookup within the parent model.

    Cursor sticks around, but mostly as a convenient way to create update functions for sub-components. We also introduce KeyedCursorProtocol which offers a keyed equivalent for dynamic lookup.

    Usage

    Sub-components become more "vanilla", just using bare properties and closures.

    struct ParentView: View {
        @StateObject = Store(
            AppModel(),
            AppEnvironment()
        )
    
        var body: some View {
            ChildModel(
                state: store.state.child,
                send: Address.forward(
                    send: store.send,
                    tag: ParentChildCursor.tag
                )
            )
        }
    }
    
    struct ChildView: View {
        var state: ChildModel
        var send: (ChildAction) -> Void
    
        var body: some View {
            Button(state.text) {
                send(.activate)
            }
        }
    }
    

    Prior art

    This approach is inspired by Reflex:

    • Forward https://github.com/mozilla/reflex/blob/c5e75e98bc601e2315b6d43e5e347263cf67359e/src/signal.js#L5
    • Cursor https://github.com/browserhtml/browserhtml/blob/master/src/Common/Cursor.js
    opened by gordonbrander 0
  • Cursors should support list items

    Cursors should support list items

    Cursors leverage static funcs for getting/setting right now, meaning they cannot support dynamic lookup. One place where you want dynamic lookup is in creating a cursor for an item in a dynamic list.

    Possible approaches

    Introduce cursor protocol that uses instance methods

    public protocol CursorProtocol {
        associatedtype Model: ModelProtocol
        associatedtype ViewModel: ModelProtocol
    
        /// Get an inner state from an outer state
        func get(state: Model) -> ViewModel
    
        /// Set an inner state on an outer state, returning an outer state
        func set(state: Model, inner: ViewModel) -> Model
    
        /// Tag an inner action, transforming it into an outer action
        func tag(_ action: ViewModel.Action) -> Model.Action
    }
    
    extension CursorProtocol {
        /// Update an outer state through a cursor.
        /// CursorProtocol.update offers a convenient way to call child
        /// update functions from the parent domain, and get parent-domain
        /// states and actions back from it.
        ///
        /// - `state` the outer state
        /// - `action` the inner action
        /// - `environment` the environment for the update function
        /// - Returns a new outer state
        public func update(
            state: Model,
            action viewAction: ViewModel.Action,
            environment: ViewModel.Environment
        ) -> Update<Model> {
            let next = ViewModel.update(
                state: self.get(state: state),
                action: viewAction,
                environment: environment
            )
            return Update(
                state: self.set(state: state, inner: next.state),
                fx: next.fx.map(self.tag).eraseToAnyPublisher(),
                transaction: next.transaction
            )
        }
    }
    

    Nit: for cursors with effectively static lifetimes, if you don't want to create a cursor for a child component every render, you need to save it somewhere. I think this is unavoidable if you want dynamic lookup.

    Introduce a concrete cursor type that leverages closures

    ...like Binding:

    public struct Cursor: CursorProtocol {
        private let _get: (Model) -> ViewModel
        private let _set: (Model, ViewModel) -> Model
        private let _tag: (ViewModel.Action) -> Model.Action
    
        /// Initialize a ViewStore using a get and send closure.
        public init(
            get: @escaping (Model) -> ViewModel,
            set: @escaping (Model, ViewModel) -> Model,
            tag: @escaping (ViewModel.Action) -> Model.Action
        ) {
            self._get = get
            self._set = set
            self._tag = tag
        }
    
        public func get(state: Model) {
            self._get(state)
        }
    
        public func set(state: Model, inner: ViewModel) {
            self._set(state, inner)
        }
    
        public func tag(_ action: ViewModel.Action) {
            self._tag(action)
        }
    }
    

    This gives you a simple syntax for one-offs via closure. The protocol allows us to hand-author optimized cursors.

    Introduce a way to wrap models with indexing information

    It may be that we can avoid instancing cursors if actions/models can be wrapped in indexing information. To investigate.

    ... I think this would complect parent and child domains.

    Introduce KeyedCursorProtocol

    A new static cursor protocol that allows for failable gets and keyed tagging fn. Details TBD.

    enhancement 
    opened by gordonbrander 0
  • Xcode14

    Xcode14 "Publishing changes from within view updates is not allowed, this will cause undefined behavior."

    New "purple warning" in Xcode 14 "Publishing changes from within view updates is not allowed, this will cause undefined behavior.", seemingly related to https://github.com/subconsciousnetwork/ObservableStore/blob/main/Sources/ObservableStore/ObservableStore.swift#L229

    self.state = next.state
    

    We must be tripping over some kind of footgun because we're not doing anything too fancy here.

    Leads:

    • https://developer.apple.com/forums/thread/711899
    • https://www.donnywals.com/xcode-14-publishing-changes-from-within-view-updates-is-not-allowed-this-will-cause-undefined-behavior/
    • https://stackoverflow.com/questions/73323869/swiftui-publishing-an-environment-change-from-within-view-update
    • https://www.hackingwithswift.com/forums/swiftui/xcode-14-publishing-changes-from-within-view-updates-is-not-allowed-this-will-cause-undefined-behavior/16434
    apple-bug 
    opened by gordonbrander 2
Releases(0.2.0)
  • 0.2.0(Sep 16, 2022)

    Version 0.2.0 is an important update to ObservableStore. It introduces the ability to create scoped stores for sub-components, called ViewStores. ViewStores are conceptually like bindings to stores, except that they expose the store API, instead of a binding API. This approach is inspired by the component mapping pattern from Elm.

    Changes

    • Introduce StoreProtocol, a protocol that describes a kind of store.
    • Introduce ViewStore<Model: ModelProtocol>, which implements StoreProtocol
    • Implement StoreProtocol for Store
    • Introduce CursorProtocol, which describes how to map from one domain to another, and provides a convenience function for updating child components.
    • Introduce ModelProtocol, which implements an update(state:action:environment) static function.
      • ModelProtocol allows us to treat action and environment as associatedtypes, which simplifies many of our other type signatures.
    • Synthesize update(state:actions:environment) method for any type that implements ModelProtocol. This allows you to dispatch multiple actions immediately in sequence, effectively composing multiple actions.
      • This helper replaces pipe
      • Useful when dispatching actions down to multiple child sub-components
    • Add new tests

    Breaking changes

    • Store requires that state implement ModelProtocol. This allows us to simplify the signatures of many other APIs
      • Update type signature changes from Update<State, Action> to Update<Model: ModelProtocol>.
      • Store type signature changes from Store<State, Action, Environment> to Store<Model: ModelProtocol>
      • Store initializer changes from Store.init(update:state:environment:) to Store.init(state:environment:). Now that state conforms to ModelProtocol, you don't have to explicitly pass in the update function as a closure. We can just call the protocol implementation.
    • store.binding has been removed in favor of Binding(store:get:send:)
    • Remove Update.pipe. Redundant now. Was never happy with it anyway. It was an inelegant way to accomplish the same thing as update(state:actions:environment:).
    • Join fx on main with a .default QoS. We have reduced the QoS from .userInteractive to avoid spamming that QoS with actions. This should not affect ordinary use of ObservableStore. fx are async/never block user interaction anyway, so a default QoS should be fine.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Mar 26, 2022)

    Introduces new features, bugfixes, and cleans up geneneric type arguments, function signatures, and argument labels.

    Note for upgrading from 0.0.7 to 0.1.0: 0.1.0 includes breaking changes to APIs. Because this version is pre-1.0, and we are still working out APIs, these breaking changes are expressed as a minor revision (0.1.0) instead of a major revision.

    Release notes:

    • Changed Store generic signature to Store<State, Action, Environment>. See https://github.com/gordonbrander/ObservableStore/pull/14
    • Changed update function signature to update(State, Action, Environment). See https://github.com/gordonbrander/ObservableStore/pull/14
    • Removed argument label from send so it is just Store.send(_). See https://github.com/gordonbrander/ObservableStore/pull/10
    • Changed argument label for subscribe to Store.subscribe(to:). See https://github.com/gordonbrander/ObservableStore/pull/10
    • Introduced .binding(get:tag:) so that constructing a binding without specifying an animation will leave transaction animation untouched. See https://github.com/gordonbrander/ObservableStore/pull/12
    • Added Update.mergeFx(_) which can be used to merge additional fx into an Update. Useful when composing update functions with Update.pipe. See https://github.com/gordonbrander/ObservableStore/pull/9
    • Removed Update.transaction method. See https://github.com/gordonbrander/ObservableStore/pull/9
    Source code(tar.gz)
    Source code(zip)
Owner
Gordon Brander
Building something new (prev @google, @mitmedialab, @mozilla).
Gordon Brander
Flutter Apple Product Store App UI Home Page With Getx

Flutter Apple Product Store App UI Home Page With Getx A new Flutter UI Project on my Youtube Channel . About The Project Create a beautiful Flutter U

Muawia Saeed 11 Dec 23, 2022
🍞 Toast for Swift - Toaster Android-like toast with very simple interface

Toaster Android-like toast with very simple interface. (formerly JLToast) Screenshots Features Queueing: Centralized toast center manages the toast qu

Suyeol Jeon 1.6k Jan 3, 2023
A SwiftUI bottom-up controller, like in the Maps app. Drag to expand or minimize.

SwiftUI Drawer A SwiftUI bottom-up controller, like in the Maps app. Drag to expand or minimize. Contents Add the Package Basic Usage Examples Credits

Michael Verges 695 Jan 3, 2023
SwiftCrossUI - A cross-platform SwiftUI-like UI framework built on SwiftGtk.

SwiftCrossUI A SwiftUI-like framework for creating cross-platform apps in Swift. It uses SwiftGtk as its backend. This package is still quite a work-i

null 97 Dec 28, 2022
Swift based simple information view with pointed arrow.

InfoView View to show small text information blocks with arrow pointed to another view.In most cases it will be a button that was pressed. Example To

Anatoliy Voropay 60 Feb 4, 2022
Creating a simple selectable tag view in SwiftUI is quite a challenge. here is a simple & elegant example of it.

SwiftUI TagView Creating a simple selectable tag view in SwiftUI is quite a challenge. here is a simple & elegant example of it. Usage: Just copy the

Ahmadreza 16 Dec 28, 2022
A fancy hexagonal layout for displaying data like your Apple Watch

Hexacon is a new way to display content in your app like the Apple Watch SpringBoard Highly inspired by the work of lmmenge. Special thanks to zenly f

Gautier Gédoux 340 Dec 4, 2022
A horizontal scroll dial like Instagram.

HorizontalDial Preview Requirements iOS 8.0+ Swift 5 Storyboard support Installation CocoaPods use_frameworks! pod "HorizontalDial" Manually To instal

Lee Sun-Hyoup 210 Nov 22, 2022
You can dismiss modal viewcontroller like Facebook Messenger by pulling scrollview or navigationbar in Swift.

PullToDismiss PullToDismiss provides dismiss modal viewcontroller function like Facebook Messenger by pulling scrollview or navigationbar with smooth

Suguru Kishimoto 479 Dec 5, 2022
RangeSeedSlider provides a customizable range slider like a UISlider.

RangeSeekSlider Overview RangeSeekSlider provides a customizable range slider like a UISlider. This library is based on TomThorpe/TTRangeSlider (Objec

WorldDownTown 644 Dec 12, 2022
SAHistoryNavigationViewController realizes iOS task manager like UI in UINavigationContoller. Support 3D Touch!

SAHistoryNavigationViewController Support 3D Touch for iOS9!! SAHistoryNavigationViewController realizes iOS task manager like UI in UINavigationConto

Taiki Suzuki 1.6k Dec 29, 2022
Full configurable spreadsheet view user interfaces for iOS applications. With this framework, you can easily create complex layouts like schedule, gantt chart or timetable as if you are using Excel.

kishikawakatsumi/SpreadsheetView has moved! It is being actively maintained at bannzai/SpreadsheetView. This fork was created when the project was mov

Kishikawa Katsumi 34 Sep 26, 2022
🔍 Awesome fully customize search view like Pinterest written in Swift 5.0 + Realm support!

YNSearch + Realm Support Updates See CHANGELOG for details Intoduction ?? Awesome search view, written in Swift 5.0, appears search view like Pinteres

Kyle Yi 1.2k Dec 17, 2022
A window arrangement manager for macOS like BetterSnapTool and Magnet

A window arrangement manager for macOS like BetterSnapTool and Magnet. You can split the foremost window to the left half of the screen, the left two-thirds, etc.

Takuto NAKAMURA (Kyome) 65 Dec 9, 2022
Add the Notch on the menubar like the new MacBook Pro.

iNotch Add the Notch on the menubar like the new MacBook Pro. Installation This app works on macOS 11.0 or later. Download iNotch.zip from releases pa

Takuto NAKAMURA (Kyome) 8 Apr 3, 2022
Fully customizable Facebook reactions like control

Reactions is a fully customizable control to give people more ways to share their reaction in a quick and easy way. Requirements • Usage • Installatio

Yannick Loriot 585 Dec 28, 2022
Snapchat / Instagram Stories like progress indicator

SegmentedProgressBar A simple little control that animates segments like Snapchat or Instagram Stories. Requirements iOS 8.0+ Xcode 8 Installation Dra

Dylan Marriott 442 Dec 25, 2022
Pull up controller with multiple sticky points like in iOS Maps

PullUpController Create your own pull up controller with multiple sticky points like in iOS Maps Features Multiple sticky points Landscape support Scr

Mario Iannotta 1.2k Dec 22, 2022