{"id":19685894,"url":"https://github.com/rxswiftcommunity/rxmodal","last_synced_at":"2025-04-29T06:31:06.875Z","repository":{"id":62453148,"uuid":"335746531","full_name":"RxSwiftCommunity/RxModal","owner":"RxSwiftCommunity","description":"Subscribe to your modal flows","archived":false,"fork":false,"pushed_at":"2021-05-12T20:57:41.000Z","size":1217,"stargazers_count":29,"open_issues_count":0,"forks_count":4,"subscribers_count":8,"default_branch":"main","last_synced_at":"2024-10-30T08:18:46.870Z","etag":null,"topics":["modals","rxcocoa","rxswift","uikit"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RxSwiftCommunity.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-02-03T20:29:58.000Z","updated_at":"2024-01-04T16:54:29.000Z","dependencies_parsed_at":"2022-11-01T23:46:03.716Z","dependency_job_id":null,"html_url":"https://github.com/RxSwiftCommunity/RxModal","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxModal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxModal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxModal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxModal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RxSwiftCommunity","download_url":"https://codeload.github.com/RxSwiftCommunity/RxModal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224153275,"owners_count":17264864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["modals","rxcocoa","rxswift","uikit"],"created_at":"2024-11-11T18:24:36.357Z","updated_at":"2024-11-11T18:24:36.913Z","avatar_url":"https://github.com/RxSwiftCommunity.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"assets/RxModal_Icons.png\" width=\"750px\" alt=\"RxModal Icons\" /\u003e\n\u003cbr /\u003e\n\u003ca href=\"https://cocoapods.org/pods/RxModal\" alt=\"RxModal on CocoaPods\" title=\"RxModal on CocoaPods\"\u003e\u003cimg src=\"https://img.shields.io/cocoapods/v/RxModal.svg\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/apple/swift-package-manager\" alt=\"RxModal on Swift Package Manager\" title=\"RxModal on Swift Package Manager\"\u003e\u003cimg src=\"https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# RxModal\n\n**RxModal** enforces the simple idea that a modal flow can be considered as a simple asynchroneous event:\n- the view controller is presented on subscribe\n- the user do what they want to do in the modal view\n- the view controller is dismissed on dispose and eventually emit a value or an error\n\n# Usage\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth width=\"30%\"\u003eHere's an example\u003c/th\u003e\n    \u003cth width=\"30%\"\u003eIn Action\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n  \u003ctd\u003e\u003cpre lang=\"swift\"\u003e\n  let mailComposer = RxModal.mailComposer {\n      $0.setToRecipients([\n          \"rxmodal@rxswiftcommunity.org\"\n      ])\n      $0.setMessageBody(\n          \"Hello World!\",\n          isHTML: false\n      )\n  }\n  let messageComposer = RxModal.messageComposer {\n      $0.recipients = [\"0639981337\"]\n      $0.body = \"Hello World!\"\n  }\n  contactUsButton\n      .rx.tap\n      .flatMapFirst { [unowned contactUsButton] in\n          RxModal.actionSheet(\n              source: .bounds(contactUsButton),\n              actions: [\n                  .default(\n                      title: \"Mail\",\n                      flatMapTo: mailComposer\n                  ),\n                  .default(\n                      title: \"Message\",\n                      flatMapTo: messageComposer\n                  ),\n                  .cancel(title: \"Cancel\")\n              ])\n      }\n      .subscribe()\n      .disposed(by: disposeBag)\n    \u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n    \u003cimg src=\"assets/RxModal_Demo.gif\"/\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n# Supported Modals\n\n```swift\n// MFMailComposeViewController\nRxModal.mailComposer() -\u003e Single\u003cMFMailComposeResult\u003e\n\n// MFMessageComposeViewController\nRxModal.messageComposer() -\u003e Single\u003cMessageComposeResult\u003e\n\n// MPMediaPickerController\nRxModal.mediaPicker() -\u003e Single\u003cMPMediaItemCollection\u003e\n\n// PHPickerViewController\nRxModal.photoPicker() -\u003e Single\u003c[PHPickerResult]\u003e\n\n// ASWebAuthenticationSession\nRxModal.webAuthenticationSession(url:callbackURLScheme:) -\u003e Single\u003cURL\u003e\n\n// UIAlertController\nRxModal.alert\u003cT\u003e(title:message:textFields:actions:) -\u003e Observable\u003cT\u003e\nRxModal.actionSheet\u003cT\u003e(source:title:message:actions:) -\u003e Observable\u003cT\u003e\n```\n\n### Presenter\n\nAll these functions also include a `presenter: Presenter` argument that allows you to choose where the modal will be presented. \nPresenters are just lazy `UIViewController` getters: \n```swift\n.viewController(_:) -\u003e $0\n.view(_:) -\u003e $0.window?.rootViewController\n.window(_:) -\u003e $0.rootViewController \n.scene(_:) -\u003e $0.windows.first?.rootViewController\n.keyWindow -\u003e UIApplication.shared.keyWindow?.rootViewController\n```\nDefault is `.keyWindow`. On iPad or macCatalyst allowing multiple windows, we discourage you to use `.keyWindow` or `.scene(_:)` as it might select the wrong window.\n\n### Configuration \n\nThese functions also include a configuration closure : `(ViewController) -\u003e Void` that will let you configure the view controller before presentation.\n\nIf a modal requires some parameters at `init` time, they will be part of the `RxModal` function (ex: `ASWebAuthenticationSession`, `UIAlertController`).\n\n### Preconditions\n\nSome RxModals perform precondition checks before presenting the modal and emit a `RxModalError.unsupported` if they aren't fulfilled:\n- `RxModal.mailComposer()` → `MFMailComposeViewController.canSendMail()`\n- `RxModal.messageComposer()` → `MFMessageComposeViewController.canSendText()`\n\nSome RxModals perform an authorization status check before presenting the modal and either request authorization, or emit a `RxModalError.authorizationStatusDenied(Any)` if authorization is denied:\n- `RxModal.mediaPicker()` → `MPMediaLibrary.authorizationStatus()`\n\n### Dialogs\n\n`RxModal.alert()` and `RxModal.actionSheet()` allows you to define actions that are converted to a new Observable stream, a value, or an error:\n```swift\nDialogAction\u003cT\u003e.default(title:flatMapTo: Observable\u003cT\u003e)\nDialogAction\u003cT\u003e.default(title:mapTo: T) // == flatMapTo: Observable.just(T)\nDialogAction\u003cT\u003e.default(title:throw: Error) // == flatMapTo: Observable.error(Error)\nDialogAction\u003cT\u003e.default(title:) // == flatMapTo: Observable.empty()\n```\n\n`RxModal.alert()` also let you configure alert text fields:\n```swift\nRxModal.alert(\n    title: \"Sign in\",\n    message: \"Please sign in using your credentials\",\n    textFields: [\n        DialogTextField.email { $0.placeholder = \"e-mail\" },\n        DialogTextField.password { $0.placeholder = \"password\" }\n    ],\n    actions: [\n        .cancel(title: \"Cancel\"),\n        .default(title: \"Sign In\") { textFields in\n            Credentials(\n                email: textFields[0].text ?? \"\",\n                password: textFields[1].text ?? \"\"\n            )\n        },\n    ]\n)\n```\n\n# Extending RxModal\n\nYou can easily extend RxModal with your own controllers / modal flows.\n\nIf your controller is already returning its output using Rx, it's easy:\n```swift\nclass MyModalViewController: UIViewController {\n    let myResult = PublishSubject\u003cMyResult\u003e()\n    // ...\n}\n\nextension RxModal {\n    func myModal(\n        presenter: Presenter = .keyWindow,\n        configuration: @escaping (MyModalViewController) -\u003e Void\n    ) -\u003e Single\u003cMyResult\u003e {\n        RxModalCoordinator\u003cMyModalViewController\u003e.present(using: presenter) { _ in\n            let modal = MyModalViewController()\n            configuration(modal)\n            return modal\n        } sequence: {\n            $0.viewController.myResult.asSingle()\n        }\n    }\n}\n```\n\nIf your controller is rather using a traditional `delegate` approach, you'll need to subclass `RxModalCoordinator`:\n\n```swift\nprotocol MyModalViewControllerDelegate: AnyObject {\n    func myModal(_ myModal: MyModalViewController, didFinishWith result: MyResult)\n    func myModal(_ myModal: MyModalViewController, didFinishWithError error: Error)\n}\n\nclass MyModalViewController: UIViewController {\n    weak var delegate: MyModalViewControllerDelegate?\n    // ...\n}\n\nextension RxModal {\n    func myModal(\n        presenter: Presenter = .keyWindow,\n        configuration: @escaping (MyModalViewController) -\u003e Void\n    ) -\u003e Single\u003cMyResult\u003e {\n        \n        MyModalViewControllerCoordinator.present(using: presenter) { coordinator in\n            let modal = MyModalViewController()\n            modal.delegate = coordinator\n            configuration(modal)\n            return modal\n        } sequence: {\n            $0.myResult.asSingle()\n        }\n        \n    }\n}\n\nfinal class MyModalViewControllerCoordinator: RxModalCoordinator\u003cMyModalViewController\u003e, MyModalViewControllerDelegate {\n    required init(){}\n    \n    let myResult = PublishSubject\u003cMyResult\u003e()\n    \n    func myModal(_ myModal: MyModalViewController, didFinishWith result: MyResult) {\n        myResult.onNext(result)\n        myResult.onCompleted()\n    }\n    \n    func myModal(_ myModal: MyModalViewController, didFinishWithError error: Error) {\n        myResult.onError(error)\n    }\n\n}\n```\n\nIf your controller is embedded in a non `UIViewController` object, you won't be able to leverage on `RxModalCoordinator` and you'll need to handle all the present/dismiss boilerplate. See [ASWebAuthenticationSession.swift](./Sources/Other/ASWebAuthenticationSession.swift) as an example.\n\n\n# Author\n\n[Jérôme Alves](https://twitter.com/jegnux)\n\n# License\n\n**RxModal** is available under the MIT license. See the [LICENSE](LICENSE) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frxswiftcommunity%2Frxmodal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frxswiftcommunity%2Frxmodal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frxswiftcommunity%2Frxmodal/lists"}