ModernAVPlayer is a persistence AVPlayer wrapper

Last update: Jun 21, 2022

ModernAVPlayer

Swift 4.2 Build Status CocoaPods CocoaPods

ModernAVPlayer is a persistence AVPlayer wrapper

++ Cool features ++

  • Get 9 nice and relevant player states (playing, buffering, loading, loaded...)
  • Persistence player to resume playback after bad network connection even in background mode (bug from version 1.5.1)
  • Manage headphone interactions, call & siri interruptions, now playing informations
  • Add your own plug-in to manage tracking, events...
  • RxSwift compatible
  • Loop mode
  • Log available by domain

Known issue

From version 1.5.1, resume playback from background mode failed. If you have any suggestion, please help.

Use of mixWithOther AVAudiosession CategoryOptions is not a solution.


Menu

Requirements

  • iOS 10.0+
  • tvOS 10.0+

In order to support background mode, append the following to your Info.plist:

UIBackgroundModes

    audio

Installation

Swift Package Manager

Supported version: swift-tools-version:5.0

// Package.swift

import PackageDescription

let package = Package(
    name: "Sample",
    dependencies: [
        .package(url: "https://github.com/noreasonprojects/ModernAVPlayer", from: "X.X.X")
    ],
    targets: [
        .target(name: "Sample", dependencies: ["ModernAVPlayer"])
    ]
)

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

CocoaPods 1.3+ is required to build ModernAVPlayer.

To integrate ModernAVPlayer into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '' do
    pod 'ModernAVPlayer'
end

Then, run the following command:

$ pod install

Getting started

Create media from URL

let media = ModernAVPlayerMedia(url: URL, type: MediaType)

Create media from AVPlayerItem

let media = ModernAVPlayerMediaItem(item: AVPlayerItem, type: MediaType, metadata: PlayerMediaMetadata)

Instanciate the wrapper

let player = ModernAVPlayer()

Load and play the media

player.load(media: media, autostart: true)

Track on repeat

player.loopMode = true
↓ State / Command → loadMedia play pause stop seek
Init O X O O X
Loading O X O O X
Loaded O O O O O
Buffering O X O O O
Playing O X O O O
Paused O O X O O
Stopped O O O X O
WaitingNetwork O X O O X
Failed O O X X X

Advanced

Custom configuration

All player configuration are available from PlayerConfiguration protocol.
A default implementation ModernAVPlayerConfiguration is provided with documentation


Remote command

If using default configuration file ( swift useDefaultRemoteCommand = true), ModernAVPlayer use automatically all commands created by ModernAVPlayerRemoteCommandFactory class Documention available in ModernAVPlayerRemoteCommandFactory.swift file

Custom command

Use your own PlayerConfiguration implementation with

...
useDefaultRemoteCommand = false
...

Create an array of commands conforming to ModernAVPlayerRemoteCommand protocol.

let player = ModernAVPlayer(config: YourConfigImplementation())
let commands: [ModernAVPlayerRemoteCommand] = YourRemoteCommandFactory.commands
player.remoteCommands = commands

You can use existing commands from public ModernAVPlayerRemoteCommandFactory class.


Plugin

Use PlayerPlugin protocol to create your own plugin system, like tracking Plugin.


RxSwift

Instead of using delegate pattern, you can use rx to bind player attributes.

Setup

Use pod 'ModernAVPlayer/RxSwift' in the Podfile

Usage

let player = ModernAVPlayer()
let state: Observable = player.rx.state

Communication

  • If you found a bug, make a pull request using Simple Audio template in the example section to demonstrate.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.

GitHub

https://github.com/noreasonprojects/ModernAVPlayer
Comments
  • 1. Sometimes player.rx.itemDuration returns NaN

    if I tap on the next, previous button in the player screen or remote control center. Sometimes player.rx.itemDuration return a NaN value. Thus, we cannot change the position of the slider time running.

    // Display item duration
    player.rx.itemDuration
    .observeOn(concurrentBackgroundScheduler)
    .filter { $0 != nil }
    .map { String(format: "%.2f", $0!) }
    .asDriver(onErrorJustReturn: "error")
    .drive(onNext: setEndDurationTime)
    .disposed(by: disposeBag)
    
      func setEndDurationTime(dur: String?) {
        // dur: NaN sometimes if we play quickly
            if let time = dur?.toDouble().playTimeString {
                self.lblEnd.text = time
            }
        }
        
    
    Reviewed by dlpigpen at 2019-07-11 04:17
  • 2. Player Playing issue

    Audio doesn't play sometime. It hang up between Loading — Loaded — Buffering - Waiting For Network - Loading — Loaded — Buffering — Waiting For Network - Loading... . Sometime audio played but takes too much time.

    Logs :

    02:31:15076 [🔈][PlayerContext.swift]:85 :: Loading 2018-08-20 14:31:15.092040+0530 AudecibelPodcast[3935:1169149] CredStore - performQuery - Error copying matching creds. Error=-25300, query={ class = inet; "m_Limit" = "m_LimitAll"; "r_Attributes" = 1; sync = syna; } 2018-08-20 14:31:15.099437+0530 AudecibelPodcast[3935:1163830] [framework] CUICatalog: Invalid asset name supplied: ''

    02:31:18381 [🔈][PlayerContext.swift]:85 :: Loaded

    02:31:18382 [🔈][PlayerContext.swift]:85 :: Buffering
    2018-08-20 14:31:18.387123+0530 AudecibelPodcast[3935:1163830] [framework] CUICatalog: Invalid asset name supplied: ''
    
    02:31:21389 [🔈][PlayerContext.swift]:85 :: Waiting For Network
    2018-08-20 14:31:21.396182+0530 AudecibelPodcast[3935:1163830] [framework] CUICatalog: Invalid asset name supplied: ''
    2018-08-20 14:31:21.399674+0530 AudecibelPodcast[3935:1169965] Task <BD7D92BA-908D-41B6-AAD7-0C88881EAC01>.<1> finished with error - code: -999
    
    02:31:21888 [🔈][PlayerContext.swift]:85 :: Loading
    2018-08-20 14:31:21.905569+0530 AudecibelPodcast[3935:1169965] CredStore - performQuery - Error copying matching creds.  Error=-25300, query={
        class = inet;
        "m_Limit" = "m_LimitAll";
        "r_Attributes" = 1;
        sync = syna;
    }
    2018-08-20 14:31:21.915624+0530 AudecibelPodcast[3935:1163830] [framework] CUICatalog: Invalid asset name supplied: ''
    
    02:31:24953 [🔈][PlayerContext.swift]:85 :: Loaded
    ~~~ PLUGIN: customAttribute=attribute0
    02:31:24954 [🔈][PlayerContext.swift]:85 :: Buffering
    2018-08-20 14:31:24.959167+0530 AudecibelPodcast[3935:1163830] [framework] CUICatalog: Invalid asset name supplied: ''
    
    Please Help me solved this problem.
    Thanks
    Reviewed by gaganshrm23 at 2018-08-20 09:04
  • 3. Paused audio not resuming after phone call

    Hi,

    When a call recieved, or switched to camera app to take video, after interruption endded audio is not started.. i'm testing on iOS 13.6..

    IS there a way to override interruption callbacks?. is The code below responsible for not resuming the audio playback after interruption?

    /* Do not set any call back on interruption ended when user play from another app */ private func pauseByInterruption() { let state = PausedState(context: context) if !audioSession.secondaryAudioShouldBeSilencedHint { state.onInterruptionEnded = { [weak state] in state?.play() } } changeState(state: state) }

    Reviewed by barisyazganc at 2020-08-01 09:29
  • 4. ModernAVPlayerNowPlayingService.overrideInfoCenter Crashes

    Hi, some of our users experiencing fatal crash in our app

    Crash is related to ModernAVPlayer - Line 4362331420 ModernAVPlayerNowPlayingService.overrideInfoCenter(for:value:)

    and NowPlayingService.swift - Line 92 closure #1 in ModernAVPlayerNowPlayingService.updateRemoteImage(url:) + 92

    - Line 4362332792 thunk for @escaping @callee_guaranteed (@guaranteed Data?, @guaranteed NSURLResponse?, @guaranteed Error?) -> () + 4362332792

    i think it happens when we provide the remoteimage..

    i personally cant reproduce this crash but firebase crashlytics keeps popping this crash..

    Crashed: NSOperationQueue 0x105d5fc40 (QOS: UNSPECIFIED) EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000000

    we cant remove support for remoteimage because nowplaying info albumart is remote image and we dont use image cache so it must be downloaded using this method,

        let playerMetaData = ModernAVPlayerMediaMetadata(title: title,
                                                      albumTitle: albumTitle,
                                                      artist: artist,
                                                      image: placeHolderImageData,
                                                      remoteImageUrl: remoteImageURL)
    

    player.updateMetadata(playerMetaData)

    am i missing something or using this method wrong?

    Reviewed by barisyazganc at 2020-05-21 18:58
  • 5. Crash on PlayingState.swift - Line 193 PlayingState.routeAudioChanged(reason:) + 193

    Hi,

    I came across a strange crash on Firebase...

    Fatal Exception: NSInvalidArgumentException An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer. PlayingState.routeAudioChanged(reason:)

    Trace:

    -[AVPlayer _removeAllLayers]

    PlayingState.swift - Line 193 PlayingState.routeAudioChanged(reason:) + 193

    PlayingState.swift - Line 65 partial apply for closure #1 in PlayingState.init(context:itemPlaybackObservingService:routeAudioService:interruptionAudioService:audioSession:) + 65

    RouteAudioService.swift - Line 63 ModernAVPlayerRouteAudioService.audioRouteChanged(notification:) + 63

    Reviewed by barisyazganc at 2020-06-03 18:50
  • 6. How to Remove NowPlayingInfo?

    Hi. Thanks for making a good library.

    I know that NowPlayingInfo is updated automatically when I update ModernAVPlayerMediaMetadata, but I don't know how to remove NowPlayingInfo.

    How to Remove NowPlayingInfo?

    Reviewed by vkcldhkd at 2020-06-01 00:48
  • 7. Previous item continues playing

    Hello!

    I'm learning iOS by writing Podcasts app. And I have 2 issues:

    1. Previous item continues playing when I tap on another episode from my list. I have noticed that when I tap "Stop" from Control Center and then tap on another episode, everything's working correctly.

    2. I need to show how much time is left, but I couldn't find how can I get a duration of current media.

    I would really appreciate it if you could help me. I'm using PlayerService as a wrapper for all of the player methods. And I present the player from EpisodesViewController. You can look at my code here: https://github.com/Karambirov/Podcasts/tree/feature/player-service

    Reviewed by Karambirov at 2019-04-09 17:54
  • 8. Xcode 10 Swift 4.2

    Hi,

    After updating the pods ModernAVPlayerMedia is taking assetOptions dictionary in the constructor.

    let media = ModernAVPlayerMedia(assetOptions: nil, url: liveUrl, type: .stream, isLive: true, metadata: nil)

    Can you share what is assetOptions and how we can use it?

    Thanks

    Reviewed by ArbabR at 2018-09-28 19:18
  • 9. prevTrackCommand, nextTrackCommand are not available

    prevTrackCommand, nextTrackCommand are not available even if I added them into remoteCommands. But skipBackwardCommand(), skipForwardCommand() commands work well. I want prev and next commands work, please help

    Reviewed by tauypaldiyev at 2020-07-23 03:53
  • 10. feat: add accurate seek api

    Feature: Add precise seek api that utilizes Apple's seek(to:tolearanceBefore:toleranceAfter:)

    seek(position: Double, isAccurate: Bool)
    seek(offset: Double, isAccurate: Bool)
    

    Tests are missing, had troubles with regenerating Mocks, I will appreciate if you can help me with this @raphrel 🙏

    Reviewed by yaroslavlvov at 2021-01-11 14:35
  • 11. Possible support for tvOS

    Hi,

    is it possible to support tvOS without any development? i want to use modernavplayer with my tvOS target but pod install says its not compatible with tvOS

    Reviewed by barisyazganc at 2020-08-11 19:46
  • 12. Bump jmespath from 1.4.0 to 1.6.1

    Bumps jmespath from 1.4.0 to 1.6.1.

    Release notes

    Sourced from jmespath's releases.

    Release v1.6.1 - 2022-03-07

    • Issue - Use JSON.parse instead of JSON.load.

    Release v1.6.0 - 2022-02-14

    • Feature - Add support for string comparissons.

    Release v1.5.0 - 2022-01-10

    • Support implicitly convertible objects/duck-type values responding to to_hash and to_ary.

      [See related GitHub pull request #51](jmespath/jmespath.rb#51).

    Changelog

    Sourced from jmespath's changelog.

    1.6.1 (2022-03-07)

    • Issue - Use JSON.parse instead of JSON.load.

    1.6.0 (2022-02-14)

    • Feature - Add support for string comparisons.

    1.5.0 (2022-01-10)

    • Support implicitly convertible objects/duck-type values responding to to_hash and to_ary.

      [See related GitHub pull request #51](jmespath/jmespath.rb#51).

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    Reviewed by dependabot[bot] at 2022-06-07 21:39
  • 13. Bump cocoapods-downloader from 1.4.0 to 1.6.3

    Bumps cocoapods-downloader from 1.4.0 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1

    Enhancements
    • None.
    Bug Fixes
    • Fix "can't modify frozen string" errors when pods are integrated using the branch option
      buju77 #10920

    1.5.0

    ... (truncated)

    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1 (2021-09-07)

    Enhancements
    • None.

    ... (truncated)

    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    Reviewed by dependabot[bot] at 2022-04-05 22:21
  • 14. i got two crash was positioned in ModernAVPlayerRemoteCommandFactory file

    1、ModernAVPlayerRemoteCommandFactoryV04playE0AA0bcdE0VyFSo08MPRemoteE13HandlerStatusVSo0hE5EventCcfU0_ 2、ModernAVPlayerRemoteCommandFactoryV015togglePlayPauseE0AA0bcdE0VyFSo08MPRemoteE13HandlerStatusVSo0jE5EventCcfU0_

    Reviewed by AL-bruce at 2021-12-29 06:58
  • 15. Audio stops and won't resume after phone call

    Hi,

    There is a strange behavior, audio pauses and continues after recieving phone call, its fine and expected behavior but when you try to make a call while audio is playing, audio stops but won't resume playing after phone call ends....

    Reviewed by barisyazganc at 2021-09-01 08:40
:musical_note: A Mac app wrapper for music.youtube.com
:musical_note: A Mac app wrapper for music.youtube.com

A simple Mac app wrapper using WKWebView for YouTube Music that allows YouTube Music to run as a standalone process. Features Media Keys Keyboard shor

Jun 24, 2022
SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way.
SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way.

What is SugarRecord? SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way. Than

Jun 16, 2022
VIMVideoPlayer is a simple wrapper around the AVPlayer and AVPlayerLayer classes.

VIMVideoPlayer is a simple wrapper around the AVPlayer and AVPlayerLayer classes.

May 11, 2022
DataKernel is a minimalistic wrapper around CoreData stack to ease persistence operations.

DataKernel What is DataKernel? DataKernel is a minimalistic wrapper around CoreData stack to ease persistence operations. It is heavily inspired by Su

Jun 2, 2022
AudioPlayer is syntax and feature sugar over AVPlayer. It plays your audio files (local & remote).

AudioPlayer AudioPlayer is a wrapper around AVPlayer. It also offers cool features such as: Quality control based on number of interruption (buffering

Jun 24, 2022
BMPlayer - A video player for iOS, based on AVPlayer, support the horizontal, vertical screen
BMPlayer - A video player for iOS, based on AVPlayer, support the horizontal, vertical screen

A video player for iOS, based on AVPlayer, support the horizontal, vertical screen. support adjust volume, brightness and seek by slide, support subtitles.

Jun 22, 2022
Player View is a delegated view using AVPlayer of Swift

PlayerView [![CI Status](http://img.shields.io/travis/David Alejandro/PlayerView.svg?style=flat)](https://travis-ci.org/David Alejandro/PlayerView) An

May 31, 2022
Swifty360Player - iOS 360-degree video player streaming from an AVPlayer.
Swifty360Player - iOS 360-degree video player streaming from an AVPlayer.

Swifty360Player iOS 360-degree video player streaming from an AVPlayer. Demo Requirements Swifty360Player Version Minimum iOS Target Swift Version 0.2

Jun 10, 2022
Gumlet analytics integration with AVPlayer for iOS native applications.

gumlet-Insights-avplayer Gumlet Insights integration with AVPlayer for iOS native applications. This Insights enables you to get useful data about vid

Dec 15, 2021
NYT360Video plays 360-degree video streamed from an AVPlayer on iOS.
NYT360Video plays 360-degree video streamed from an AVPlayer on iOS.

NYT360Video 360º video playback from The New York Times NYT360Video plays spherical 360º video, allowing the user to explore the video via pan gesture

May 11, 2022
SuperPlayer is a library to wrap AVPlayer with Composable Architecture.
SuperPlayer is a library to wrap AVPlayer with Composable Architecture.

SuperPlayer is a library to wrap AVPlayer with Composable Architecture. It can be used in SwiftUI and UIKit.

Jun 7, 2022
iOS 360-degree video player streaming from an AVPlayer.
iOS 360-degree video player streaming from an AVPlayer.

Swifty360Player iOS 360-degree video player streaming from an AVPlayer. Demo Requirements Swifty360Player Version Minimum iOS Target Swift Version 0.2

Jun 10, 2022
Pretty iOS mobile screens + AVPlayer video view ––– made in SwiftUI
Pretty iOS mobile screens + AVPlayer video view ––– made in SwiftUI

UrbanVillageProjectScreens Recreated UI screens from the conceptual Urban Village Project. Read more about the project here. Please open an issue if y

Jun 13, 2022
JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box.
JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box.

JustPersist JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box. It also allows you to migrate to

Mar 13, 2022
Store and retrieve Codable objects to various persistence layers, in a couple lines of code!
Store and retrieve Codable objects to various persistence layers, in a couple lines of code!

tl;dr You love Swift's Codable protocol and use it everywhere, who doesn't! Here is an easy and very light way to store and retrieve Codable objects t

Jun 14, 2022
🛶Shallows is a generic abstraction layer over lightweight data storage and persistence.

Shallows Shallows is a generic abstraction layer over lightweight data storage and persistence. It provides a Storage<Key, Value> type, instances of w

Jun 16, 2022
SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.
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

Jun 24, 2022
iOS app to record how much things cost using various data persistence implementations.
iOS app to record how much things cost using various data persistence implementations.

how-much iOS app to record how much things cost using various data persistence implementations. The basic data unit is an item, a simple dictionary: {

Feb 7, 2022
SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.
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

Jun 26, 2022
Job Scheduler for IOS with Concurrent run, failure/retry, persistence, repeat, delay and more

SwiftQueue Schedule tasks with constraints made easy. SwiftQueue is a job scheduler for iOS inspired by popular android libraries like android-priorit

Jun 21, 2022