A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.

Overview

Swift Identified Collections

CI

A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.

Motivation

When modeling a collection of elements in your application's state, it is easy to reach for a standard Array. However, as your application becomes more complex, this approach can break down in many ways, including accidentally making mutations to the wrong elements or even crashing. 😬

For example, if you were building a "Todos" application in SwiftUI, you might model an individual todo in an identifiable value type:

struct Todo: Identifiable {
  var description = ""
  let id: UUID
  var isComplete = false
}

And you would hold an array of these todos as a published field in your app's view model:

class TodosViewModel: ObservableObject {
  @Published var todos: [Todo] = []
}

A view can render a list of these todos quite simply, and because they are identifiable we can even omit the id parameter of List:

struct TodosView: View {
  @ObservedObject var viewModel: TodosViewModel
  
  var body: some View {
    List(self.viewModel.todos) { todo in
      ...
    }
  }
}

If your deployment target is set to the latest version of SwiftUI, you may be tempted to pass along a binding to the list so that each row is given mutable access to its todo. This will work for simple cases, but as soon as you introduce side effects, like API clients or analytics, or want to write unit tests, you must push this logic into a view model, instead. And that means each row must be able to communicate its actions back to the view model.

You could do so by introducing some endpoints on the view model, like when a row's completed toggle is changed:

class TodosViewModel: ObservableObject {
  ...
  func todoCheckboxToggled(at id: Todo.ID) {
    guard let index = self.todos.firstIndex(where: { $0.id == id })
    else { return }
    
    self.todos[index].isComplete.toggle()
    // TODO: Update todo on backend using an API client
  }
}

This code is simple enough, but it can require a full traversal of the array to do its job.

Perhaps it would be more performant for a row to communicate its index back to the view model instead, and then it could mutate the todo directly via its index subscript. But this makes the view more complicated:

List(self.viewModel.todos.enumerated(), id: \.element.id) { index, todo in
  ...
}

This isn't so bad, but at the moment it doesn't even compile. An evolution proposal may change that soon, but in the meantime List and ForEach must be passed a RandomAccessCollection, which is perhaps most simply achieved by constructing another array:

List(Array(self.viewModel.todos.enumerated()), id: \.element.id) { index, todo in
  ...
}

This compiles, but we've just moved the performance problem to the view: every time this body is evaluated there's the possibility a whole new array is being allocated.

But even if it were possible to pass an enumerated collection directly to these views, identifying an element of mutable state by an index introduces a number of other problems.

While it's true that we can greatly simplify and improve the performance of any view model methods that mutate an element through its index subscript:

class TodosViewModel: ObservableObject {
  ...
  func todoCheckboxToggled(at index: Int) {
    self.todos[index].isComplete.toggle()
    // TODO: Update todo on backend using an API client
  }
}

Any asynchronous work that we add to this endpoint must take great care in not using this index later on. An index is not a stable identifier: todos can be moved and removed at any time, and an index identifying "Buy lettuce" at one moment may identify "Call Mom" the next, or worse, may be a completely invalid index and crash your application!

class TodosViewModel: ObservableObject {
  ...
  func todoCheckboxToggled(at index: Int) async {
    self.todos[index].isComplete.toggle()
    
    do {
      // ❌ Could update the wrong todo, or crash!
      self.todos[index] = try await self.apiClient.updateTodo(self.todos[index]) 
    } catch {
      // Handle error
    }
  }
}

Whenever you need to access a particular todo after performing some asynchronous work, you must do the work of traversing the array:

class TodosViewModel: ObservableObject {
  ...
  func todoCheckboxToggled(at index: Int) async {
    self.todos[index].isComplete.toggle()
    
    // 1️⃣ Get a reference to the todo's id before kicking off the async work
    let id = self.todos[index].id
  
    do {
      // 2️⃣ Update the todo on the backend
      let updatedTodo = try await self.apiClient.updateTodo(self.todos[index])
              
      // 3️⃣ Find the updated index of the todo after the async work is done
      let updatedIndex = self.todos.firstIndex(where: { $0.id == id })!
      
      // 4️⃣ Update the correct todo
      self.todos[updatedIndex] = updatedTodo
    } catch {
      // Handle error
    }
  }
}

Introducing: identified collections

Identified collections are designed to solve all of these problems by providing data structures for working with collections of identifiable elements in an ergonomic, performant way.

Most of the time, you can simply swap an Array out for an IdentifiedArray:

import IdentifiedCollections

class TodosViewModel: ObservableObject {
  @Published var todos: IdentifiedArrayOf<Todo> = []
  ...
}

And then you can mutate an element directly via its id-based subscript, no traversals needed, even after asynchronous work is performed:

class TodosViewModel: ObservableObject {
  ...
  func todoCheckboxToggled(at id: Todo.ID) async {
    self.todos[id: id]?.isComplete.toggle()
    
    do {
      // 1️⃣ Update todo on backend and mutate it in the todos identified array.
      self.todos[id: id] = try await self.apiClient.updateTodo(self.todos[id: id]!)
    } catch {
      // Handle error
    }

    // No step 2️⃣ 😆
  }
}

You can also simply pass the identified array to views like List and ForEach without any complications:

List(self.viewModel.todos) { todo in
  ...
}

Identified arrays are designed to integrate with SwiftUI applications, as well as applications written in the Composable Architecture.

Design

IdentifiedArray is a lightweight wrapper around the OrderedDictionary type from Apple's Swift Collections. It shares many of the same performance characteristics and design considerations, but is better adapted to solving the problem of holding onto a collection of identifiable elements in your application's state.

IdentifiedArray does not expose any of the details of OrderedDictionary that may lead to breaking invariants. For example an OrderedDictionary<ID, Identifiable> may freely hold a value whose identifier does not match its key or multiple values could have the same id, and IdentifiedArray does not allow for these situations.

And unlike OrderedSet, IdentifiedArray does not require that its Element type conforms to the Hashable protocol, which may be difficult or impossible to do, and introduces questions around the quality of hashing, etc.

IdentifiedArray does not even require that its Element conforms to Identifiable. Just as SwiftUI's List and ForEach views take an id key path to an element's identifier, IdentifiedArrays can be constructed with a key path:

var numbers = IdentifiedArray(id: \Int.self)

Performance

IdentifiedArray is designed to match the performance characteristics of OrderedDictionary. It has been benchmarked with Swift Collections Benchmark:

Installation

You can add Identified Collections to an Xcode project by adding it as a package dependency.

https://github.com/pointfreeco/swift-identified-collections

If you want to use Identified Collections in a SwiftPM project, it's as simple as adding a dependencies clause to your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.1.0")
],

Documentation

The latest documentation for Identified Collections' APIs is available here.

Interested in learning more?

These concepts (and more) are explored thoroughly in Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.

Usage of IdentifiedArray in the Composable Architecture was explored in the following Point-Free episode:

video poster image

License

All modules are released under the MIT license. See LICENSE for details.

Comments
  • Support OS versions below iOS 13, macOS 10.15 etc.

    Support OS versions below iOS 13, macOS 10.15 etc.

    Since this repo is now a dependency of TCA, and I have been updating RAS-TCA to match, it forces me to remove support for older Apple OSs for RAS-TCA as well 😢

    I tried various different approaches to try and make IdentifiedCollections support the older OSes without success.

    If you have any ideas on how that might work I'd be interested 😄

    In the meantime, I might have to keep the "old" IdentifiedArray in RAS-TCA until a better solution can be found.

    opened by mluisbrown 12
  • Getting

    Getting "Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range" error when trying to nil a state

    Hi I have following case and state and I am trying to clear a state when user dismiss a view controller:

    var userProfileStates: IdentifiedArrayOf<User.State> = .init()

    case let .clearStates(id):
        if !id.isEmpty, state.userProfileStates[id: id] != nil {
    
            // try to remove by getting a mutable compy of array
            var states = state.userProfileStates
            states[id: id] = nil
            state.userProfileStates = states //This crashes
    
            // Try to remove by ID
            state.userProfileStates[id: id] = nil //Also crashes
    
            // Try to remove by remove
            state.userProfileStates.remove(id: id) //Also crashes
    
             // Try to remove by removeAll
            state.userProfileStates.removeAll { $0.id == id } //Also crashes
        }
        state.addedUserState = nil
        state.followingState = nil
        state.followerState = nil
        return .none
    

    But I am getting this error with all four of the approaches above:

    Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range

    These are the steps I produce the error:

    • Fetch a user profile data
    • Create a User.State with user data
    • Asing the new state to both a single variable to observe and open the UserProfileView and also append it to userProfileStates identified array to scope the state
    state.userProfileStates.append(newState)
    state.addedUserState = newState
    ...
    
    viewStore
        .publisher
        .addedUserState
        .sink { [weak self] state in
            self?.handleNewUserStore(state?.id ?? "")
        }
        .store(in: &cancellables)
    ...
    
    internal func handleNewUserStore(_ id: String) {
        guard let nc = getNavController() else { return }
        guard let index = viewStore.userProfileStates.firstIndex (where: { $0.id == id }) else { return }
        showUserProfile(
            nc,
            store: store.scope(
                state: \.userProfileStates[index],
                action: { .userProfileAction(id: id, action: $0) }
            )
        )
    }
    
    ...
    
    internal func showUserProfile(
        _ navController: UINavigationController?,
        store: UserStore
    ) {
        guard let parent = viewStore.parentViewController else { return }
        let view = UserProfileView(
            store: store,
            parent: parent,
            delegate: self
        )
        let controller = UIHostingController(rootView: view)
        controller.hidesBottomBarWhenPushed = false
        navController?.pushViewController(controller, animated: true)
        navController?.setNavigationBarHidden(true, animated: true)
    }
    
    • Tap back button on UserProfileView and send the user state id with the back button delegate method to clear out the state

    If i set a break point before trying to nil the state I can see that the particular state is exist:

    (lldb) po state.userProfileStates
    
    ▿ 1 element
      ▿ 0 : State
        - id : "3eccebc5-05bd-4623-8e59-665db69afb80"
        ...
    

    Lib version: 0.5.0

    UPDATE: Sending the clear action with a delay partially solved my problem:

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
        self?.viewStore.send(.clearStates(id))
    }
    

    This trick solves the problem when user taps to back button and clears out the particular state.

    But if I push multiple user screens and tap a different tab bar item, all the sub view controllers are dismissed by tab bar and I should remove all the child states. For this one app still crashes.

    Here is the stack trace for the case where user tapped to a tab bar item:

    Thread 1 Queue : com.apple.main-thread (serial)

    #0	0x000000018bfa78e0 in _swift_runtime_on_report ()
    #1	0x000000018c034580 in _swift_stdlib_reportFatalErrorInFile ()
    #2	0x000000018bce2e10 in closure #1 in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:) ()
    #3	0x000000018bce2b78 in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:) ()
    #4	0x000000018bce2564 in _assertionFailure(_:_:file:line:flags:) ()
    #5	0x000000018bd23ca0 in ContiguousArray.subscript.getter ()
    #6	0x0000000103a7833c in OrderedDictionary.Values.subscript.getter at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-collections/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Values.swift:284
    #7	0x0000000103a52324 in IdentifiedArray.subscript.read at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-identified-collections/Sources/IdentifiedCollections/IdentifiedArray/IdentifiedArray+MutableCollection.swift:5
    #8	0x0000000103bed6b4 in key path getter for IdentifiedArray.subscript(_:) : IdentifiedArray<String, User.State>StringUser.State ()
    #9	0x000000018bdcddcc in RawKeyPathComponent._projectReadOnly<τ_0_0, τ_0_1, τ_0_2>(_:to:endingWith:) ()
    #10	0x000000018bdcd8d4 in closure #1 in KeyPath._projectReadOnly(from:) ()
    #11	0x000000018bdd1040 in swift_getAtKeyPath ()
    #12	0x0000000103bed538 in implicit closure #2 in implicit closure #1 in MainTabBarViewController.handleNewUserStore(_:) ()
    #13	0x0000000103bed7e0 in partial apply for implicit closure #2 in implicit closure #1 in MainTabBarViewController.handleNewUserStore(_:) ()
    #14	0x00000001038cf00c in closure #3 in ScopedReducer.rescope<τ_0_0, τ_0_1, τ_0_2, τ_0_3>(_:state:action:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:650
    #15	0x00000001038cf49c in partial apply for closure #3 in ScopedReducer.rescope<τ_0_0, τ_0_1, τ_0_2, τ_0_3>(_:state:action:) ()
    #16	0x000000019b33d8e4 in Subscribers.Sink.receive(_:) ()
    #17	0x000000019b33df04 in protocol witness for Subscriber.receive(_:) in conformance Subscribers.Sink<τ_0_0, τ_0_1> ()
    #18	0x000000019b3f4b4c in Publishers.Drop.Inner.receive(_:) ()
    #19	0x000000019b3f4e74 in protocol witness for Subscriber.receive(_:) in conformance Publishers.Drop<τ_0_0>.Inner<τ_1_0> ()
    #20	0x000000019b3ab418 in CurrentValueSubject.Conduit.offer(_:) ()
    #21	0x000000019b3ac250 in partial apply for closure #1 in CurrentValueSubject.send(_:) ()
    #22	0x000000019b38070c in partial apply for thunk for @callee_guaranteed (@guaranteed ConduitBase<τ_0_0, τ_0_1>) -> (@error @owned Error) ()
    #23	0x000000018bd68588 in Sequence.forEach(_:) ()
    #24	0x000000019b380124 in ConduitList.forEach(_:) ()
    #25	0x000000019b3aa65c in CurrentValueSubject.send(_:) ()
    #26	0x000000019b3ac214 in specialized CurrentValueSubject.value.setter ()
    #27	0x000000019b3aa550 in CurrentValueSubject.value.setter ()
    #28	0x00000001038c8ca8 in $defer #1 <τ_0_0, τ_0_1>() in Store.send(_:originatingFrom:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:336
    #29	0x00000001038c8a0c in Store.send(_:originatingFrom:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:332
    #30	0x000000010399e358 in closure #1 in ViewStore.init(_:removeDuplicates:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:203
    #31	0x000000010399ea78 in ViewStore.send(_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:281
    #32	0x0000000103bd3e00 in MainTabBarView.didSelectItem(at:) at /Users/olcayertas/popcorn-ios/Packages/MainTabBar/Sources/MainTabBar/MainTabBarView.swift:104
    #33	0x0000000103befb28 in MainTabBarViewController.tabBar(_:didSelect:) at /Users/olcayertas/popcorn-ios/Packages/MainTabBar/Sources/MainTabBar/MainTabBarViewController.swift:168
    #34	0x0000000103befd40 in @objc MainTabBarViewController.tabBar(_:didSelect:) ()
    #35	0x000000011f3ff51c in -[UITabBar _sendAction:withEvent:] ()
    #36	0x000000011fccd920 in -[UIApplication sendAction:to:from:forEvent:] ()
    #37	0x000000011f6879cc in -[UIControl sendAction:to:forEvent:] ()
    #38	0x000000011f687d10 in -[UIControl _sendActionsForEvents:withEvent:] ()
    #39	0x000000011f401d1c in -[UITabBar _buttonUp:] ()
    #40	0x000000011fccd920 in -[UIApplication sendAction:to:from:forEvent:] ()
    #41	0x000000011f6879cc in -[UIControl sendAction:to:forEvent:] ()
    #42	0x000000011f687d10 in -[UIControl _sendActionsForEvents:withEvent:] ()
    #43	0x000000011f686a30 in -[UIControl touchesEnded:withEvent:] ()
    #44	0x000000011fd02ae8 in -[UIWindow _sendTouchesForEvent:] ()
    #45	0x000000011fd03f90 in -[UIWindow sendEvent:] ()
    #46	0x000000011fce2224 in -[UIApplication sendEvent:] ()
    #47	0x000000011fd5eb10 in __dispatchPreprocessedEventFromEventQueue ()
    #48	0x000000011fd60774 in __processEventQueue ()
    #49	0x000000011fd59650 in __eventFetcherSourceCallback ()
    #50	0x00000001803731dc in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
    #51	0x0000000180373124 in __CFRunLoopDoSource0 ()
    #52	0x0000000180372894 in __CFRunLoopDoSources0 ()
    #53	0x000000018036cf00 in __CFRunLoopRun ()
    #54	0x000000018036c7f4 in CFRunLoopRunSpecific ()
    #55	0x0000000188faec98 in GSEventRunModal ()
    #56	0x000000011fcc85d4 in -[UIApplication _run] ()
    #57	0x000000011fccc5cc in UIApplicationMain ()
    #58	0x000000010b0f4fc0 in UIApplicationMain(_:_:_:_:) ()
    #59	0x0000000102d95dac in static UIApplicationDelegate.main() ()
    #60	0x0000000102d95d34 in static AppDelegate.$main() at /Users/olcayertas/popcorn-ios/Pop/AppDelegate.swift:21
    #61	0x0000000102d9624c in main ()
    #62	0x00000001089a1fa0 in start_sim ()
    #63	0x0000000108ac5e50 in start ()
    

    And this is the stack trace for TCA debug tread:

    Thread 14 Queue : co.pointfree.ComposableArchitecture.DebugEnvironment (serial)

    #0	0x000000010324a404 in static SharedUIModel.Address.__derived_struct_equals(_:_:) ()
    #1	0x000000010448a9a4 in static Home.State.__derived_struct_equals(_:_:) ()
    #2	0x000000010448de64 in protocol witness for static Equatable.== infix(_:_:) in conformance Home.State ()
    #3	0x0000000103086108 in static CoW<τ_0_0>.== infix(_:_:) at /Users/olcayertas/popcorn-ios/Packages/Common/Sources/Common/Helpers/CoW.swift:39
    #4	0x0000000103ba06a4 in static MainTabBar.State.__derived_struct_equals(_:_:) ()
    #5	0x0000000103bac51c in protocol witness for static Equatable.== infix(_:_:) in conformance MainTabBar.State ()
    #6	0x0000000103a454d8 in static Box<τ_0_0>.isEqual(_:_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-custom-dump/Sources/CustomDump/Internal/Box.swift:11
    #7	0x0000000103a457b0 in protocol witness for static AnyEquatable.isEqual(_:_:) in conformance <τ_0_0> Box<τ_0_0> ()
    #8	0x0000000103a45f74 in open #1 <τ_0_0>(_:) in isMirrorEqual(_:_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-custom-dump/Sources/CustomDump/Internal/Box.swift:17
    #9	0x0000000103a458d4 in isMirrorEqual(_:_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-custom-dump/Sources/CustomDump/Internal/Box.swift:19
    #10	0x0000000103a30d30 in diff<τ_0_0>(_:_:format:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-custom-dump/Sources/CustomDump/Diff.swift:546
    #11	0x00000001038a77f0 in closure #1 in print #1 <τ_0_0, τ_0_1, τ_0_2><τ_1_0, τ_1_1>@Sendable () in closure #1 in AnyReducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift:206
    #12	0x00000001038aa6c0 in partial apply for closure #1 in print #1 <τ_0_0, τ_0_1, τ_0_2><τ_1_0, τ_1_1>@Sendable () in closure #1 in AnyReducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) ()
    #13	0x00000001038a7e98 in thunk for @escaping @callee_guaranteed @Sendable () -> () ()
    #14	0x000000010c424594 in _dispatch_call_block_and_release ()
    #15	0x000000010c425d5c in _dispatch_client_callout ()
    #16	0x000000010c42e040 in _dispatch_lane_serial_drain ()
    #17	0x000000010c42ed80 in _dispatch_lane_invoke ()
    #18	0x000000010c43cb40 in _dispatch_workloop_worker_thread ()
    #19	0x00000001af2568fc in _pthread_wqthread ()
    Enqueued from com.apple.main-thread (Thread 1) Queue : com.apple.main-thread (serial)
    #0	0x000000010c42b0b8 in dispatch_async ()
    #1	0x00000001a10ec0ac in _swift_dispatch_async ()
    #2	0x00000001a10eaf48 in OS_dispatch_queue.async(group:qos:flags:execute:) ()
    #3	0x00000001038a74c8 in print #1 <τ_0_0, τ_0_1, τ_0_2><τ_1_0, τ_1_1>@Sendable () in closure #1 in AnyReducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift:196
    #4	0x00000001038a80f8 in closure #2 in closure #1 in AnyReducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift:221
    #5	0x00000001038aa1e4 in partial apply for closure #2 in closure #1 in AnyReducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) ()
    #6	0x000000010387b708 in closure #1 in closure #1 in static EffectPublisher.fireAndForget(_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Effects/Publisher.swift:273
    #7	0x000000010387d648 in partial apply for closure #1 in closure #1 in static EffectPublisher.fireAndForget(_:) ()
    #8	0x00000001af05d090 in TaskLocal.withValue<τ_0_0>(_:operation:file:line:) ()
    #9	0x000000010387b4d0 in closure #1 in static EffectPublisher.fireAndForget(_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Effects/Publisher.swift:272
    #10	0x000000019b33c0d4 in Deferred.receive<τ_0_0>(subscriber:) ()
    #11	0x000000019b3d00a8 in PublisherBox.receive<τ_0_0>(subscriber:) ()
    #12	0x000000019b3d02f8 in AnyPublisher.receive<τ_0_0>(subscriber:) ()
    #13	0x0000000103878688 in EffectPublisher.receive<τ_0_0>(subscriber:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Effects/Publisher.swift:13
    #14	0x000000010387963c in protocol witness for Publisher.receive<τ_0_0>(subscriber:) in conformance EffectPublisher<τ_0_0, τ_0_1> ()
    #15	0x000000019b38ba48 in Publishers.Merge.receive<τ_0_0>(subscriber:) ()
    #16	0x000000019b3d00a8 in PublisherBox.receive<τ_0_0>(subscriber:) ()
    #17	0x000000019b3d02f8 in AnyPublisher.receive<τ_0_0>(subscriber:) ()
    #18	0x000000019b3d07f4 in Publishers.Map.receive<τ_0_0>(subscriber:) ()
    #19	0x000000019b3d00a8 in PublisherBox.receive<τ_0_0>(subscriber:) ()
    #20	0x000000019b3d02f8 in AnyPublisher.receive<τ_0_0>(subscriber:) ()
    #21	0x000000019b3c56ec in Publishers.HandleEvents.receive<τ_0_0>(subscriber:) ()
    #22	0x000000019b33d1f8 in Publisher.sink(receiveCompletion:receiveValue:) ()
    #23	0x00000001038c840c in Store.send(_:originatingFrom:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:372
    #24	0x00000001038cde94 in ScopedReducer.reduce(into:action:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:612
    #25	0x00000001038ce404 in protocol witness for ReducerProtocol.reduce(into:action:) in conformance ScopedReducer<τ_0_0, τ_0_1, τ_0_2, τ_0_3> ()
    #26	0x00000001038c7ee4 in Store.send(_:originatingFrom:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:352
    #27	0x000000010399e358 in closure #1 in ViewStore.init(_:removeDuplicates:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:203
    #28	0x000000010399ea78 in ViewStore.send(_:) at /Users/olcayertas/Library/Developer/Xcode/DerivedData/Pop-cpngppyhjpuxaufspkgplncwputt/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:281
    #29	0x0000000103bd3e00 in MainTabBarView.didSelectItem(at:) at /Users/olcayertas/popcorn-ios/Packages/MainTabBar/Sources/MainTabBar/MainTabBarView.swift:104
    #30	0x0000000103befb28 in MainTabBarViewController.tabBar(_:didSelect:) at /Users/olcayertas/popcorn-ios/Packages/MainTabBar/Sources/MainTabBar/MainTabBarViewController.swift:168
    #31	0x0000000103befd40 in @objc MainTabBarViewController.tabBar(_:didSelect:) ()
    #32	0x000000011f3ff51c in -[UITabBar _sendAction:withEvent:] ()
    #33	0x000000011fccd920 in -[UIApplication sendAction:to:from:forEvent:] ()
    #34	0x000000011f6879cc in -[UIControl sendAction:to:forEvent:] ()
    #35	0x000000011f687d10 in -[UIControl _sendActionsForEvents:withEvent:] ()
    #36	0x000000011f401d1c in -[UITabBar _buttonUp:] ()
    #37	0x000000011fccd920 in -[UIApplication sendAction:to:from:forEvent:] ()
    #38	0x000000011f6879cc in -[UIControl sendAction:to:forEvent:] ()
    #39	0x000000011f687d10 in -[UIControl _sendActionsForEvents:withEvent:] ()
    #40	0x000000011f686a30 in -[UIControl touchesEnded:withEvent:] ()
    #41	0x000000011fd02ae8 in -[UIWindow _sendTouchesForEvent:] ()
    #42	0x000000011fd03f90 in -[UIWindow sendEvent:] ()
    #43	0x000000011fce2224 in -[UIApplication sendEvent:] ()
    #44	0x000000011fd5eb10 in __dispatchPreprocessedEventFromEventQueue ()
    #45	0x000000011fd60774 in __processEventQueue ()
    #46	0x000000011fd59650 in __eventFetcherSourceCallback ()
    #47	0x00000001803731dc in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
    #48	0x0000000180373124 in __CFRunLoopDoSource0 ()
    #49	0x0000000180372894 in __CFRunLoopDoSources0 ()
    #50	0x000000018036cf00 in __CFRunLoopRun ()
    #51	0x000000018036c7f4 in CFRunLoopRunSpecific ()
    #52	0x0000000188faec98 in GSEventRunModal ()
    #53	0x000000011fcc85d4 in -[UIApplication _run] ()
    #54	0x000000011fccc5cc in UIApplicationMain ()
    #55	0x000000010b0f4fc0 in UIApplicationMain(_:_:_:_:) ()
    #56	0x0000000102d95dac in static UIApplicationDelegate.main() ()
    #57	0x0000000102d95d34 in static AppDelegate.$main() at /Users/olcayertas/popcorn-ios/Pop/AppDelegate.swift:21
    #58	0x0000000102d9624c in main ()
    #59	0x00000001089a1fa0 in start_sim ()
    #60	0x0000000108ac5e50 in start ()
    #61	0x542c800000000000 in 0x542c800000000000 ()
    
    opened by olcayertas 6
  • Unavailable position-based subscript setter crashes on runtime

    Unavailable position-based subscript setter crashes on runtime

    After upgrading from TCA's IdentifiedArray to the version in this repo (0.1.1), my app is crashing in places where it used the position-based subscript setter.

    There's a @available annotation in the code, but that doesn't show up (at least not in Xcode 12.5). It's the fatalError in the setter that hits.

    The weird thing is that the error did show up in another place in my code.

    I think it's unfortunate that has become a runtime issue, but I'm not sure how to fix that. I'm also not sure why the setter can't be implemented. I can live without it (I only use it in tests), but it was convenient to have.

    opened by Thomvis 6
  • Add IdentifiedSet with SetAlgebra conformance

    Add IdentifiedSet with SetAlgebra conformance

    I often find myself using types like Dictionary<IdentifiableValue.ID, IdentifiableValue> to hold information. I’d prefer to use a Set, but I need O(1) subscript lookup.

    IdentifiedArray is almost perfect for my needs, but maintains order instead of using set operations. Would it be possible to make an unordered counterpart?

    opened by Saklad5 4
  • [SPM] Dependencies could not be resolved in project that already imports `swift-collections`

    [SPM] Dependencies could not be resolved in project that already imports `swift-collections`

    First of all, thank you for making this library! It's something I've thought about a number of times since starting with SwiftUI and am glad there's finally a solid implementation I can use.

    There appears to be an issue importing this dependency via SPM in a project that already imports the swift-collections package (as mine does). I use OrderedDictionary for something unrelated to my use case for IdentifiedArray, but when I try to add the swift-identified-collections package to my manifest, I get the following error:

    Dependencies could not be resolved because root depends on 'swift-identified-collections' 0.1.0..<1.0.0.
    'swift-identified-collections' cannot be used because 'swift-identified-collections' depends on 'swift-collections' 0.0.4..<1.0.0 and root depends on 'swift-collections' 0.0.3.
    

    Presumably, this is caused by the fact that your library also depends on swift-collections, and SPM doesn't let you import the same package twice.

    If I am able to access OrderedDictionary transitively via swift-identified-collections, then that could be a way of solving this problem (I haven't tried yet), but I'd prefer to maintain a more descriptive manifest that imports both packages separately if at all possible.

    swift-identified-collections-import-error

    opened by JUSTINMKAUFMAN 3
  • Eliminate platform requirement

    Eliminate platform requirement

    @mluisbrown I realized #8 was still open and was hoping to close it out.

    A quick test and I can't get the array literal to compile when targeting iOS 11, which seems to indicate that the latest Xcode release has fixed the bug you were experiencing?

    opened by stephencelis 2
  • IdentifiedArray Subscript Read crash

    IdentifiedArray Subscript Read crash

    Hello,

    I'm migrating an app to TCA and experiencing crashes in IdentifiedArray.subscript.read during an equality check. Unfortunately I haven't been able to reproduce this case in an isolated example yet.

    I haven't been able to reproduce the crash in a predictable way with my app either (it seems to appear 'randomly') – but it seems to be triggered by the use Reducer.debug.

    Here is my stack trace:

    #0	0x0000000188588cf4 in _swift_runtime_on_report ()
    #1	0x0000000188608e60 in _swift_stdlib_reportFatalErrorInFile ()
    #2	0x000000018823e23c in closure #1 in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:) ()
    #3	0x000000018823dfdc in closure #1 in closure #1 in _assertionFailure(_:_:file:line:flags:) ()
    #4	0x000000018823d8d0 in _assertionFailure(_:_:file:line:flags:) ()
    #5	0x000000018828a0b4 in ContiguousArray.subscript.getter ()
    #6	0x0000000104a75860 in OrderedDictionary.Elements.subscript.getter at [...]/checkouts/swift-collections/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift:273
    #7	0x0000000104a54260 in IdentifiedArray.subscript.read at [...]/checkouts/swift-identified-collections/Sources/IdentifiedCollections/IdentifiedArray/IdentifiedArray+Collection.swift:17
    #8	0x0000000104a54a04 in protocol witness for Collection.subscript.read in conformance IdentifiedArray<τ_0_0, τ_0_1> ()
    #9	0x000000018827c408 in protocol witness for IteratorProtocol.next() in conformance IndexingIterator<τ_0_0> ()
    #10	0x00000001883b53a8 in Sequence<>.elementsEqual<τ_0_0>(_:) ()
    #11	0x0000000105945b38 in static FriendManagerState.== infix(_:_:) at [...]/Sources/FriendProfileState/FriendManagerState.swift:36
    #12	0x0000000105945dd8 in protocol witness for static Equatable.== infix(_:_:) in conformance FriendManagerState ()
    #13	0x00000001882a1748 in static Optional<τ_0_0>.== infix(_:_:) ()
    #14	0x0000000104a4f6bc in static Box<τ_0_0>.isEqual(_:_:) at [...]/checkouts/swift-custom-dump/Sources/CustomDump/Internal/Box.swift:11
    #15	0x0000000104a4f764 in protocol witness for static AnyEquatable.isEqual(_:_:) in conformance <τ_0_0> Box<τ_0_0> ()
    #16	0x0000000104a4fe24 in open #1 <τ_0_0>(_:) in isMirrorEqual(_:_:) at [...]/checkouts/swift-custom-dump/Sources/CustomDump/Internal/Box.swift:17
    #17	0x0000000104a4f8c4 in isMirrorEqual(_:_:) at [...]/checkouts/swift-custom-dump/Sources/CustomDump/Internal/Box.swift:19
    #18	0x0000000104a3e8bc in diff<τ_0_0>(_:_:format:) at [...]/checkouts/swift-custom-dump/Sources/CustomDump/Diff.swift:518
    #19	0x00000001049219ac in closure #1 in closure #1 in closure #1 in Reducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) at [...]/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Debugging/ReducerDebugging.swift:127
    #20	0x00000001049235d4 in partial apply for closure #1 in closure #1 in closure #1 in Reducer.debug<τ_0_0, τ_0_1>(_:state:action:actionFormat:environment:) ()
    #21	0x0000000104922068 in thunk for @escaping @callee_guaranteed () -> () ()
    #22	0x0000000109b40a20 in _dispatch_call_block_and_release ()
    #23	0x0000000109b42700 in _dispatch_client_callout ()
    #24	0x0000000109b4a83c in _dispatch_lane_serial_drain ()
    #25	0x0000000109b4b558 in _dispatch_lane_invoke ()
    #26	0x0000000109b57fa0 in _dispatch_workloop_worker_thread ()
    #27	0x00000001f33a91b0 in _pthread_wqthread ()
    

    From what I've been able to debug, at some point IdentifiedArray.subscript(position:Int) is called with an invalid position. The _keys and _values elements of _dictionary go out of sync (in my case, _keys contains 14 elements whereas _dictionary only contains 13 – which causes an out of range access).

    For performances reason, one of my TCA state is a class, not a struct, and the crash occurs when its equality is checked. I'm using IdentifiedArray in a lot of other places in struct states and haven't experienced any crash – so this might be related, I guess?

    Here is a simplified version of my class:

    public final class FriendManagerState: Equatable {
      public internal(set) var friends: IdentifiedArrayOf<FriendProfileState>
    
      public init(
        friends: IdentifiedArrayOf<FriendProfileState>
      ) {
        self.friends = friends
      }
    
      public static func == (
        lhs: FriendManagerState, rhs: FriendManagerState
      ) -> Bool {
        // This is the method invoked at step 11 of the stacktrace
    
        // Also crashes with `lhs.friends.elementsEqual(rhs.friends.elements)`
        return lhs.friends == rhs.friends
      }
    }
    

    I'm not doing anything fancy with the array, the only methods I'm calling are updateOrInsert, update, append, sort – and everything is performed on a single queue.


    Version: 0.3.1 Xcode 13.0 (release) Swift 5.5 (swiftlang-1300.0.31.1 clang-1300.0.29.1) The crash occurs on iOS 15.0 & 15.1, I haven't been able to test on MacOS.

    I'm happy to provide more details if needed – also if this turns out to be a TCA issue, I'll gladly move this issue to its repo.

    apple bug 
    opened by Antonito 2
  • IdentifiedArray Equatable issues due to KeyPath

    IdentifiedArray Equatable issues due to KeyPath

    While using TCA we are running into issues where our tests are failing equality tests. The culprits are IdentifiedArray that use different initializers.

    Here is some super simplified code that illustrates the problem:

    class StateTests: XCTestCase {
        func testStateEquality() {
            struct SomeItem: Identifiable, Equatable {
                var id: String = "item-id"
            }
            struct State: Equatable {
                var items = IdentifiedArray<String, SomeItem>(id: \.id)
            }
            // this happens in the reducer
            var state = State()
            state.items.append(SomeItem())
    
            // this happens in our test
            let expectedState = State(items: .init(uniqueElements: [SomeItem()]))
            XCTAssertEqual(state, expectedState, "This is going to fail :(")
        }
    }
    
    

    This code looks sensible and I would assume that the test would pass. But the problem is if you use an initializer that has a KeyPath input parameter vs. one that uses generics to create the KeyPath, then the equality comparison will fail, even if they have all of the same elements. The reason is that the comparison of the KeyPaths fails.

    Here is a quick unit test that shows the issue

    class KeyPathTests: XCTestCase {
        func testKeyPathEquality() {
            struct Struct: Identifiable {
                let id: String = "id"
            }
            func problem<T: Identifiable>(keyPath: KeyPath<T, T.ID>) {
                let keyPath1: KeyPath<T, T.ID> = \.id
                XCTAssertEqual(keyPath1, keyPath, "This is going to fail :(")
            }
            problem(keyPath: \Struct.id)
        }
    }
    

    Unfortunately, there doesn't seem to be a proper fix for this.

    I would propose one of the following solutions: option 1:
    Remove the key path comparison in the Equatable extension. Wouldn't equality of the elements or dictionary be enough?

    option 2: Deprecate all the inits that don't have a key path parameter. Force developers to always specify the keypath.

    Hopefully I missed something and there is a proper fix for this.

    opened by sroche27r 2
  • Question not issue regarding IdentifiedArray

    Question not issue regarding IdentifiedArray

    In the implementation of IdentifiedArray what makes it possible for it to be initialized as an array like the code shown below:

    @Published var tasks2: IdentifiedArray<CKRecord.ID, TaskItem> = []

    opened by azamsharp 1
  • Update swift-collections dependency to 1.0.2

    Update swift-collections dependency to 1.0.2

    This PR updates the swift collections dependency to 1.0.2. This should fix #26.

    More information:

    "Fixed a value semantic violation in OrderedSet and OrderedDictionary that could result in some mutation methods corrupting shared copies of the same value, leading to subsequent crashes. (Issue #104)"

    https://github.com/apple/swift-collections/releases/tag/1.0.2

    opened by KaiOelfke 1
  • CPU performance issues

    CPU performance issues

    Discussed in https://github.com/pointfreeco/swift-identified-collections/discussions/28

    Originally posted by mihaho October 13, 2021 I am facing some issues when trying to upgrade from Composable Architecture v20 to a higher version. The problem is that the CPU starts spiking after the introduction of the Identified Collections package. I profiled the app and noticed that the largest stack trace was due to the equality checks. This drew my attention to the equality conformance of IdentifiedArray. The difference with the old version is that you now use elementsEqual instead of ==. I checked the difference in Swift's source code. elementsEqual always creates 2 iterators for each sequence and goes through all elements while == has some shortcuts.

    Replacing lhs.elementsEqual(rhs) with lhs.elements == rhs.elements helps a lot. The app becomes responsive again and the CPU drops for about ~50%

    Is elementsEqual needed? Any comments are welcome

    opened by mihaho 1
  • Add DocC and GitHub Issue Templates

    Add DocC and GitHub Issue Templates

    Started this branch to encourage issues to include more info, but noticed we were missing a link to DocC-based documentation, so quickly prepared DocC generation, as well.

    opened by stephencelis 0
  • Crash when used with rearrangeable SwiftUI List

    Crash when used with rearrangeable SwiftUI List

    I'm using var items: IdentifiedArrayOf<MyType> with SwiftUI's List($items, editActions: .all) and once I rearrange any element in the list the app crashes with Precondition failed: Element identity must remain constant

    opened by okla 2
Releases(0.5.0)
  • 0.5.0(Nov 14, 2022)

    What's Changed

    • Added: IdentifiedArray now conforms to Sendable, MutableCollection, and RangeReplaceableCollection (#41).

    Full Changelog: https://github.com/pointfreeco/swift-identified-collections/compare/0.4.1...0.5.0

    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Sep 12, 2022)

  • 0.4.0(May 20, 2022)

    • Added: IdentifiedArray.append(contentsOf:), which appends elements to an array iff they are not already present (thanks @jeffersonsetiawan).
    • Updated: swift-collections dependency has been updated to 1.0.2 (thanks @KaiOelfke).
    • Updated: Identified Collections platform requirements have been relaxed.
    Source code(tar.gz)
    Source code(zip)
  • 0.3.2(Oct 20, 2021)

  • 0.3.1(Oct 8, 2021)

  • 0.3.0(Sep 13, 2021)

  • 0.2.0(Aug 25, 2021)

    • Added: IdentifiedArray.elements, an array view of the identified array (thanks @p4checo).
    • Changed: IdentifiedArray.move(fromOffsets:toOffset:) and IdentifiedArray.remove(atOffsets:) no longer requires SwiftUI (thanks @gonzalolarralde).
    • Improved: IdentifiedArray.move(fromOffsets:toOffset:) and IdentifiedArray.remove(atOffsets:) are now much more performant.
    • Fixed: IdentifiedArray.partition now partitioning keys along with their values by calling down to the correct underlying implementation (thanks @tgt).
    • Fixed: stopped using a few APIs from Swift Collections that have been deprecated (thanks @diederich).
    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Jul 13, 2021)

  • 0.1.0(Jul 11, 2021)

    • Initial release!

    What are the main differences between this library and the IdentifiedArray that shipped in swift-composable-architecture 0.20.0?

    • It is now a wrapper around OrderedDictionary from Swift Collections.

      This means we can lean on the performance and correctness of Apple's library and avoid many of the bugs discovered in the original implementation.

    • It no longer conforms to MutableCollection and RangeReplaceableCollection.

      We take the same position Swift Collections takes with OrderedSet and OrderedDictionary. All elements in an identified array should have unique identity, operations such as MutableCollection's subscript setter or RangeReplaceableCollection's replaceSubrange assume the ability to insert/replace arbitrary elements in the collection, but allowing that could lead to duplicate values. Methods have been deprecated or made unavailable with suggested migration paths.

    Source code(tar.gz)
    Source code(zip)
Owner
Point-Free
A video series exploring Swift and functional programming.
Point-Free
This framework contains SBB (Swiss Federal Railways) UI elements for iOS SwiftUI Apps

Framework: Design System Mobile for iOS & SwiftUI This framework contains SBB (Swiss Federal Railways) UI elements for iOS SwiftUI Apps. It allows an

Swiss Federal Railways (SBB) 21 Nov 3, 2022
UDF (Unidirectional Data Flow) is a library based on Unidirectional Data Flow pattern.

UDF (Unidirectional Data Flow) is a library based on Unidirectional Data Flow pattern. It lets you build maintainable, testable, and scalable apps.

inDriver 51 Dec 23, 2022
SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.

Articles related to this project Clean Architecture for SwiftUI Programmatic navigation in SwiftUI project Separation of Concerns in Software Design C

Alexey Naumov 4k Jan 8, 2023
Magic-8-Ball-iOS13 - Training project /Working with View

Magic-8-Ball-iOS13 Training project /Working with View Simulator.Screen.Recordin

Alexandr 0 Feb 2, 2022
Notes App using Core Data for persisting the data ✨

Notes Notes app where we can save and manage our daily notes. App usage The app allow us to add new notes We can delete our existing notes We can edit

Chris 0 Nov 13, 2021
A document-based SwiftUI application for viewing and editing EEG data, aimed at making software for viewing brain imaging data more accessible.

Trace A document-based SwiftUI application for viewing and editing EEG data, aimed at making software for viewing brain imaging data more accessible.

Tahmid Azam 3 Dec 15, 2022
A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

donggyu 3 Jun 10, 2022
CZWeatherKit is a simple, extensible weather library for iOS, tvOS, and OS X that allows for easy fetching of weather data from various weather services.

CZWeatherKit is a simple, extensible weather library for iOS, tvOS, and OS X that allows for easy fetching of weather data from various weather services.

Comyar 455 Nov 20, 2022
iOS application to tell the time in the British way 🇬🇧⏰

Tell Time ???? ⏰ As a French guy in London, when people told me the time, I was always lost. Now thanks to this app, I can confirm what I hear and wha

Renaud Jenny 50 Dec 18, 2022
The easiest way to install and switch between multiple versions of Xcode - with a mouse click.

Xcodes.app The easiest way to install and switch between multiple versions of Xcode. If you're looking for a command-line version of Xcodes.app, try x

Robots and Pencils 4.5k Dec 26, 2022
A mobile application project designed for everybody which provides the easiest way to make searchs for public services

A mobile application project designed for everybody which provides the easiest way to make searchs for public services

null 0 Nov 23, 2021
Shazam, out of your way

SLAM SLAM: an app I made in 24 hours that’s like Shazam, but out of your way. I used it as an excuse to play with ShazamKit, the new framework made by

Linus Skucas 8 Jan 24, 2022
A MVVM Project using two way binding with DidSet swift feature

FawryTask Description Fawry Task is a MVVM Project using two way binding with DidSet swift feature , follow Solid princepl by uncle bob esecially Sing

null 1 Dec 6, 2021
A Swifty way to toggle your features.

Requirements Installation Usage TODO Communication Credits License Requirements iOS 8.0+ Xcode 8.1+ Swift 3.1+ Installation CocoaPods Create a Podfile

Marco Santarossa 11 Aug 10, 2021
VerticalTabView is a native way to display paged vertical content in SwiftUI.

VerticalTabView ?? VTabView is a native way to display paged vertical content in SwiftUI. To work it makes use of the new iOS 14 TabView PageTabViewSt

Lorenzo Fiamingo 25 Dec 16, 2022
Introducing SwiftUI. A declarative way to create User Interfaces with Swift.

SwiftUI - Landmarks Introducing SwiftUI. A declarative way to create User Interfaces with Swift. SwiftUI was introduced at WWDC 2019 by Apple. It is t

Alex Paul 8 Sep 21, 2021
ReleaseNotesKit - a brand new, elegant, and extremely simple way to present the recent version’s release notes to your users

ReleaseNotesKit This is ReleaseNotesKit, a brand new, elegant, and extremely simple way to present the recent version’s release notes to your users. R

Swapnanil Dhol 22 Jun 30, 2022
ReactionButton is a control that allows developers to add this functionality to their apps in an easy way.

Since Facebook introduced reactions in 2016, it became a standard in several applications as a way for users to interact with content. ReactionButton is a control that allows developers to add this functionality to their apps in an easy way.

Jorge Ovalle 305 Oct 11, 2022