├── .gitignore ├── .travis.yml ├── Cartfile ├── Cartfile.resolved ├── Concurrency ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── CustomRefresh ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CustomRefreshViewController.swift ├── Info.plist ├── RxTableViewSectionedAnimatedOrReloadDataSource.swift └── Support │ ├── NumberSection.swift │ └── Randomizer.swift ├── Form ├── AppDelegate.swift ├── Assets.xcassets │ ├── 19635237.imageset │ │ ├── 19635237.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Cell │ ├── AvatarTableViewCell.swift │ ├── DateSelectTableViewCell.swift │ ├── DisplayTableViewCell.swift │ ├── InputTableViewCell.swift │ └── ReactiveTableViewCell.swift ├── Constellation.swift ├── ConstellationListViewController.swift ├── FormViewController.swift └── Info.plist ├── GitHubSearchRepositoriesX ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Dependencies.swift ├── Example.swift ├── GitHubSearchRepositories.storyboard ├── GitHubSearchRepositoriesAPI.swift ├── GitHubSearchRepositoriesViewController.swift ├── Info.plist ├── Services │ ├── ActivityIndicator.swift │ ├── DownloadableImage.swift │ ├── HtmlParsing.swift │ ├── PseudoRandomGenerator.swift │ ├── Randomizer.swift │ ├── Reachability.swift │ ├── ReachabilityService.swift │ ├── UIImage+Extensions.swift │ ├── UIImageView+DownloadableImage.swift │ └── Wireframe.swift ├── String+URL.swift ├── UINavigationController+Extensions.swift └── ViewController.swift ├── HandleError ├── ActivityIndicator.swift ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── QRScan │ ├── QRReadViewController.swift │ ├── QRScanResultViewController.swift │ └── RxAVCaptureMetadataOutputObjectsDelegateProxy.swift ├── Retry │ ├── RetryByUserViewController.swift │ └── RetryOrDefaultByUserViewController.swift └── UploadImageTestViewController.swift ├── LICENSE ├── LocalAuthenticationDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── LocalAuthenticationService.swift └── ViewController.swift ├── PDFExpertContents ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── arrow.imageset │ │ ├── Contents.json │ │ └── arrow.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ContentItemModel.swift ├── ExpandableItem.swift ├── ExpandedTableViewCell.swift ├── Info.plist ├── R.generated.swift ├── ViewController.swift └── contents.json ├── Questions ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── dianqk.imageset │ │ ├── Contents.json │ │ └── DianQK@200.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── OptionCollectionViewCell.swift ├── QuestionCollectionReusableView.swift ├── R.generated.swift ├── RxCollectionViewSectionedAnimatedCompletedDataSource.swift └── ViewController.swift ├── README.md ├── RxDataSourcesExample ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── DianQK.imageset │ │ ├── Contents.json │ │ ├── DianQK@200.png │ │ └── DianQK@400.png │ ├── btn_add.imageset │ │ ├── Contents.json │ │ └── btn_add.pdf │ ├── btn_delete.imageset │ │ ├── Contents.json │ │ └── btn_delete.pdf │ └── btn_delete_press.imageset │ │ ├── Contents.json │ │ └── btn_delete_press.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CellButtonClickTableViewController.swift ├── CellIdentifierTableViewController.swift ├── CellIdentifierViewController.swift ├── ChangeSwitchTableViewController.swift ├── Collection │ ├── AutomaticCollectionViewFlowLayout.swift │ ├── Collection.storyboard │ ├── CollectionViewController.swift │ ├── HUD.swift │ └── IconCell.swift ├── CustomSectionTableViewController.swift ├── Expandable │ ├── Base.lproj │ │ └── Expandable.storyboard │ ├── Cells │ │ ├── Cell+Rx.swift │ │ ├── DatePickerCell.swift │ │ ├── SliderCell.swift │ │ ├── SwitchCell.swift │ │ └── TextFieldCell.swift │ ├── Config.swift │ ├── Profile.swift │ ├── SafeCollection.swift │ └── ViewController.swift ├── Info.plist ├── MultipleCellTableViewController.swift ├── R.generated.swift ├── RefreshTableViewController.swift ├── SectionTableViewController.swift ├── TapCellTableViewController.swift ├── TipTableViewCell.swift └── TodoTableViewController.swift ├── SelectCell ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── MultiSelectTableViewController.swift ├── RadioTableViewController.swift ├── SkinDataSource.swift ├── User.swift ├── UserTableViewCell.swift └── ViewController.swift ├── Stopwatch ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── gray.imageset │ │ ├── Contents.json │ │ └── gray.pdf │ ├── green.imageset │ │ ├── Contents.json │ │ └── green.pdf │ └── red.imageset │ │ ├── Contents.json │ │ └── red.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── BasicViewModel.swift ├── Button+Style.swift ├── Cell+Rx.swift ├── FinalViewModel.swift ├── Helpers.swift ├── Info.plist ├── Input.swift ├── State.swift ├── StopwatchTypeListViewController.swift ├── StopwatchViewModelProtocol.swift ├── TimingViewModel.swift ├── Tool.swift └── ViewController.swift ├── TextInput ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── InputCollectionViewCell.swift ├── InputTextField.swift └── ViewController.swift ├── TwoWayBind ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── btn_minus_normal.imageset │ │ ├── Contents.json │ │ └── btn_minus_normal.pdf │ ├── btn_plus_normal.imageset │ │ ├── Contents.json │ │ └── btn_plus_normal.pdf │ ├── ic_right.imageset │ │ ├── Contents.json │ │ └── ic_right.pdf │ ├── ic_select.imageset │ │ ├── Contents.json │ │ └── ic_select.pdf │ ├── ic_selected.imageset │ │ ├── Contents.json │ │ └── ic_selected.pdf │ ├── purchase_icon_alipay.imageset │ │ ├── Contents.json │ │ ├── purchase_icon_alipay@2x.png │ │ └── purchase_icon_alipay@3x.png │ ├── purchase_icon_applepay.imageset │ │ ├── Contents.json │ │ └── applepay_mark.pdf │ ├── purchase_icon_unionpay.imageset │ │ ├── Contents.json │ │ ├── purchase_icon_unionpay@2x.png │ │ └── purchase_icon_unionpay@3x.png │ └── purchase_icon_wechat.imageset │ │ ├── Contents.json │ │ ├── purchase_icon_wechat@2x.png │ │ └── purchase_icon_wechat@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Cart │ ├── CartViewController.swift │ └── ProductTableViewCell.swift ├── Info.plist ├── PushSetting │ ├── PushSettingData.swift │ ├── PushSettingModel.swift │ ├── PushSettingViewController.swift │ ├── SelectTableViewCell.swift │ └── SelectTableViewHeaderFooterView.swift ├── R.generated.swift └── SelectPayment │ ├── PaymentTableViewCell.swift │ └── SelectPaymentViewController.swift ├── UploadImageSimple ├── ActivityIndicator.swift ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── UploadImages ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── PHImageManager+Rx.swift ├── QBImagePickerController+Rx.swift ├── RxQBImagePickerControllerDelegateProxy.swift ├── UploadImageCollectionViewCell.swift └── UploadImageViewController.swift ├── YepRecord ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-ipad_29@2x.png │ │ ├── icon-iphone_29@3x.png │ │ ├── icon-iphone_40@2x.png │ │ ├── icon-iphone_40@3x.png │ │ ├── icon-iphone_60@2x.png │ │ └── icon-iphone_60@3x.png │ ├── Contents.json │ ├── button_voice_pause.imageset │ │ ├── Contents.json │ │ └── button_voice_pause.pdf │ ├── button_voice_play.imageset │ │ ├── Contents.json │ │ └── button_voice_play.pdf │ ├── button_voice_reset.imageset │ │ ├── Contents.json │ │ └── button_voice_reset.pdf │ └── voice_indicator.imageset │ │ ├── Contents.json │ │ └── voice_indicator.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── NewFeedVoiceRecord │ ├── NewFeedVoiceRecord.storyboard │ └── NewFeedVoiceRecordViewController.swift ├── R.generated.swift ├── Utils │ ├── AudioBot │ │ ├── AVAudioSession+AudioBot.swift │ │ ├── AudioBot.swift │ │ └── NSFileManager+AudioBot.swift │ ├── Proposer.swift │ ├── SafeCollection.swift │ ├── UIColor+Yep.swift │ ├── UIFont+Yep.swift │ ├── YepAlert.swift │ ├── YepAudioService.swift │ └── YepConfig.swift └── Views │ ├── AudioWaves │ ├── SampleView.swift │ ├── VoiceRecordSampleView.swift │ ├── Waver.swift │ └── YepWaverView.swift │ ├── HorizontalLineView.swift │ └── RecordButton.swift ├── fastlane ├── Fastfile └── README.md └── rx-sample-code.xcodeproj ├── project.pbxproj ├── project.xcworkspace └── contents.xcworkspacedata └── xcshareddata └── xcschemes ├── Concurrency.xcscheme ├── CustomRefresh.xcscheme ├── Form.xcscheme ├── GitHubSearchRepositoriesX.xcscheme ├── HandleError.xcscheme ├── PDFExpertContents.xcscheme ├── Questions.xcscheme ├── RxDataSourcesExample.xcscheme ├── Stopwatch.xcscheme ├── TextInput.xcscheme ├── TwoWayBind.xcscheme ├── UploadImageSimple.xcscheme ├── UploadImages.xcscheme └── YepRecord.xcscheme /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | RevealServer.framework 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData/ 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata/ 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xcuserstate 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | 68 | .DS_Store 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.2 3 | xcode_project: rx-sample-code.xcodeproj 4 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" ~> 3.2 2 | github "mac-cain13/R.swift.Library" ~> 3.0 3 | github "SwiftyJSON/SwiftyJSON" ~> 3.1 4 | github "DianQK/RxExtensions" ~> 0.4 5 | github "RxSwiftCommunity/RxDataSources" ~> 1.0 6 | github "DianQK/RxAutomaton" ~> 0.2 7 | github "Alamofire/Alamofire" ~> 4.3 8 | github "jdg/MBProgressHUD" ~> 1.0 9 | github "SnapKit/SnapKit" ~> 3.1 10 | github "nvzqz/RandomKit" ~> 3.0 11 | github "questbeat/QBImagePicker" "master" 12 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.4.0" 2 | github "jdg/MBProgressHUD" "1.0.0" 3 | github "questbeat/QBImagePicker" "93ba9d54016722e82d46e1cf19750dd285b81a81" 4 | github "mac-cain13/R.swift.Library" "v3.0.2" 5 | github "ReactiveX/RxSwift" "3.3.1" 6 | github "nvzqz/ShiftOperations" "v1.0.0" 7 | github "SnapKit/SnapKit" "3.2.0" 8 | github "SwiftyJSON/SwiftyJSON" "3.1.4" 9 | github "nvzqz/RandomKit" "v3.0.0" 10 | github "DianQK/RxAutomaton" "0.2.2" 11 | github "RxSwiftCommunity/RxDataSources" "1.0.3" 12 | github "DianQK/RxExtensions" "0.4.1" 13 | -------------------------------------------------------------------------------- /Concurrency/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Concurrency 4 | // 5 | // Created by DianQK on 01/02/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Concurrency/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Concurrency/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Concurrency/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CustomRefresh/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CustomRefresh 4 | // 5 | // Created by wc on 24/12/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /CustomRefresh/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /CustomRefresh/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CustomRefresh/CustomRefreshViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomRefreshViewController.swift 3 | // CustomRefresh 4 | // 5 | // Created by wc on 24/12/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxDataSources 13 | import RxExtensions 14 | 15 | class CustomRefreshViewController: UIViewController { 16 | 17 | @IBOutlet private weak var refreshBarButtonItem: UIBarButtonItem! 18 | @IBOutlet private weak var refreshWithAnimatedBarButtonItem: UIBarButtonItem! 19 | @IBOutlet private weak var tableView: UITableView! 20 | 21 | let disposeBag = DisposeBag() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | let dataSource = RxTableViewSectionedAnimatedOrReloadDataSource() 27 | 28 | dataSource.configureCell = { dataSource, tableView, indexPath, element in 29 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 30 | cell.textLabel?.text = "\(element.number)" 31 | return cell 32 | } 33 | 34 | let initialRandomizedSections = Randomizer(rng: PseudoRandomGenerator(4, 3), sections: initialValue()) 35 | 36 | Observable.from([ 37 | refreshBarButtonItem.rx.tap.asObservable().startWith(()).replace(with: true), 38 | refreshWithAnimatedBarButtonItem.rx.tap.asObservable().replace(with: false) 39 | ]) 40 | .merge() 41 | .scan((randomizer: initialRandomizedSections, isRefresh: true)) { a, isRefresh in 42 | return (randomizer: a.randomizer.randomize(), isRefresh: isRefresh) 43 | } 44 | .map { a in 45 | return RefreshSectionList(sectionModels: a.randomizer.sections, isRefresh: a.isRefresh) 46 | } 47 | .bindTo(tableView.rx.items(dataSource: dataSource)) 48 | .disposed(by: disposeBag) 49 | 50 | } 51 | 52 | func initialValue() -> [NumberSection] { 53 | let nSections = 10 54 | let nItems = 2 55 | 56 | return (0 ..< nSections).map { (i: Int) in 57 | NumberSection(header: "Section \(i + 1)", numbers: $(Array(i * nItems ..< (i + 1) * nItems)), updated: Date()) 58 | } 59 | } 60 | 61 | } 62 | 63 | func $(_ numbers: [Int]) -> [IntItem] { 64 | return numbers.map { IntItem(number: $0, date: Date()) } 65 | } 66 | -------------------------------------------------------------------------------- /CustomRefresh/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CustomRefresh/RxTableViewSectionedAnimatedOrReloadDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxTableViewSectionedAnimatedOrReloadDataSource.swift 3 | // CustomRefresh 4 | // 5 | // Created by wc on 24/12/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxDataSources 13 | 14 | public struct RefreshSectionList { 15 | 16 | public var sectionModels: [S] 17 | public var isRefresh: Bool 18 | 19 | public init(sectionModels: [S], isRefresh: Bool) { 20 | self.sectionModels = sectionModels 21 | self.isRefresh = isRefresh 22 | } 23 | 24 | } 25 | 26 | open class RxTableViewSectionedAnimatedOrReloadDataSource: TableViewSectionedDataSource , RxTableViewDataSourceType { 27 | 28 | public typealias Element = RefreshSectionList 29 | public var animationConfiguration = AnimationConfiguration() 30 | 31 | open func tableView(_ tableView: UITableView, observedEvent: Event) { 32 | UIBindingObserver(UIElement: self) { dataSource, newSectionlist in 33 | if newSectionlist.isRefresh { 34 | dataSource.setSections(newSectionlist.sectionModels) 35 | tableView.reloadData() 36 | } 37 | else { 38 | DispatchQueue.main.async { 39 | let oldSections = dataSource.sectionModels 40 | do { 41 | let differences = try differencesForSectionedView(initialSections: oldSections, finalSections: newSectionlist.sectionModels) 42 | 43 | for difference in differences { 44 | dataSource.setSections(difference.finalSections) 45 | 46 | tableView.performBatchUpdates(difference, animationConfiguration: self.animationConfiguration) 47 | } 48 | } 49 | catch let e { 50 | print(e) 51 | self.setSections(newSectionlist.sectionModels) 52 | tableView.reloadData() 53 | } 54 | } 55 | } 56 | }.on(observedEvent) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CustomRefresh/Support/NumberSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberSection.swift 3 | // RxDataSources 4 | // 5 | // Created by Krunoslav Zaher on 1/7/16. 6 | // Copyright © 2016 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxDataSources 11 | 12 | // MARK: Data 13 | 14 | struct NumberSection { 15 | var header: String 16 | 17 | var numbers: [IntItem] 18 | 19 | var updated: Date 20 | 21 | init(header: String, numbers: [Item], updated: Date) { 22 | self.header = header 23 | self.numbers = numbers 24 | self.updated = updated 25 | } 26 | } 27 | 28 | 29 | struct IntItem { 30 | let number: Int 31 | let date: Date 32 | } 33 | 34 | // MARK: Just extensions to say how to determine identity and how to determine is entity updated 35 | 36 | extension NumberSection 37 | : AnimatableSectionModelType { 38 | typealias Item = IntItem 39 | typealias Identity = String 40 | 41 | var identity: String { 42 | return header 43 | } 44 | 45 | var items: [IntItem] { 46 | return numbers 47 | } 48 | 49 | init(original: NumberSection, items: [Item]) { 50 | self = original 51 | self.numbers = items 52 | } 53 | } 54 | 55 | extension NumberSection 56 | : CustomDebugStringConvertible { 57 | var debugDescription: String { 58 | return "NumberSection(header: \"\(self.header)\", numbers: \(numbers.debugDescription), updated: date)" 59 | } 60 | } 61 | 62 | extension IntItem 63 | : IdentifiableType 64 | , Equatable { 65 | typealias Identity = Int 66 | 67 | var identity: Int { 68 | return number 69 | } 70 | } 71 | 72 | // equatable, this is needed to detect changes 73 | func == (lhs: IntItem, rhs: IntItem) -> Bool { 74 | return lhs.number == rhs.number && lhs.date == rhs.date 75 | } 76 | 77 | // MARK: Some nice extensions 78 | extension IntItem 79 | : CustomDebugStringConvertible { 80 | var debugDescription: String { 81 | return "IntItem(number: \(number), date: date)" 82 | } 83 | } 84 | 85 | extension IntItem 86 | : CustomStringConvertible { 87 | 88 | var description: String { 89 | return "\(number)" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Form/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Form 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Form/Assets.xcassets/19635237.imageset/19635237.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/Form/Assets.xcassets/19635237.imageset/19635237.png -------------------------------------------------------------------------------- /Form/Assets.xcassets/19635237.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "19635237.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Form/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Form/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Form/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Form/Cell/AvatarTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AvatarTableViewCell.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AvatarTableViewCell: ReactiveTableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Form/Cell/DateSelectTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateSelectTableViewCell.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DateSelectTableViewCell: ReactiveTableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Form/Cell/DisplayTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DisplayTableViewCell.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DisplayTableViewCell: ReactiveTableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Form/Cell/InputTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputTableViewCell.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class InputTableViewCell: ReactiveTableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Form/Cell/ReactiveTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReactiveTableViewCell.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 25/11/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | open class ReactiveTableViewCell: UITableViewCell { 14 | 15 | public private(set) var prepareForReuseBag = DisposeBag() 16 | 17 | public let disposeBag = DisposeBag() 18 | 19 | open override func prepareForReuse() { 20 | super.prepareForReuse() 21 | prepareForReuseBag = DisposeBag() 22 | } 23 | 24 | public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 25 | super.init(style: style, reuseIdentifier: reuseIdentifier) 26 | commonInit() 27 | } 28 | 29 | public required init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | } 32 | 33 | open override func awakeFromNib() { 34 | super.awakeFromNib() 35 | commonInit() 36 | } 37 | 38 | open func commonInit() { 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Form/Constellation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constellation.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | enum Constellation { 10 | 11 | case aries 12 | case taurus 13 | case gemini 14 | case cancer 15 | case leo 16 | case virgo 17 | case libra 18 | case scorpio 19 | case sagittarius 20 | case capricorn 21 | case aquarius 22 | case pisces 23 | 24 | var name: String { 25 | switch self { 26 | case .aries: return "牡羊座" 27 | case .taurus: return "金牛座" 28 | case .gemini: return "双子座" 29 | case .cancer: return "巨蟹座" 30 | case .leo: return "狮子座" 31 | case .virgo: return "处女座" 32 | case .libra: return "天秤座" 33 | case .scorpio: return "天蝎座" 34 | case .sagittarius: return "射手座" 35 | case .capricorn: return "摩羯座" 36 | case .aquarius: return "水瓶座" 37 | case .pisces: return "双鱼座" 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Form/ConstellationListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstellationListViewController.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ConstellationListViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Form/FormViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormViewController.swift 3 | // Form 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxDataSources 13 | 14 | enum FormType { 15 | case avatar(title: String, image: Variable) 16 | case name(title: String, input: Variable) 17 | case phoneNumber(title: String, input: Variable) 18 | case birthday(title: String, birthday: Driver) 19 | case date(date: Variable) 20 | case constellation(title: String, constellation: Driver) 21 | } 22 | 23 | class FormViewController: UIViewController { 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | let avatarImage = Variable(nil) 29 | let name = Variable("") 30 | let phoneNumber = Variable("") 31 | let birthday = Variable(nil) 32 | let constellation = Variable(nil) 33 | 34 | 35 | 36 | 37 | 38 | } 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Form/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GitHubSearchRepositoriesX 4 | // 5 | // Created by DianQK on 14/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Dependencies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dependencies.swift 3 | // RxExample 4 | // 5 | // Created by carlos on 13/5/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | #if !RX_NO_MODULE 10 | import RxSwift 11 | #endif 12 | 13 | import class Foundation.URLSession 14 | import class Foundation.OperationQueue 15 | import enum Foundation.QualityOfService 16 | 17 | class Dependencies { 18 | 19 | // ***************************************************************************************** 20 | // !!! This is defined for simplicity sake, using singletons isn't advised !!! 21 | // !!! This is just a simple way to move services to one location so you can see Rx code !!! 22 | // ***************************************************************************************** 23 | static let sharedDependencies = Dependencies() // Singleton 24 | 25 | let URLSession = Foundation.URLSession.shared 26 | let backgroundWorkScheduler: ImmediateSchedulerType 27 | let mainScheduler: SerialDispatchQueueScheduler 28 | let wireframe: Wireframe 29 | let reachabilityService: ReachabilityService 30 | 31 | private init() { 32 | wireframe = DefaultWireframe() 33 | 34 | let operationQueue = OperationQueue() 35 | operationQueue.maxConcurrentOperationCount = 2 36 | #if !RX_NO_MODULE 37 | operationQueue.qualityOfService = QualityOfService.userInitiated 38 | #endif 39 | backgroundWorkScheduler = OperationQueueScheduler(operationQueue: operationQueue) 40 | 41 | mainScheduler = MainScheduler.instance 42 | reachabilityService = try! DefaultReachabilityService() // try! is only for simplicity sake 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Example.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Example.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 3/28/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | 10 | #if os(iOS) 11 | import UIKit 12 | typealias Image = UIImage 13 | #elseif os(macOS) 14 | import Cocoa 15 | import AppKit 16 | typealias Image = NSImage 17 | #endif 18 | 19 | let MB = 1024 * 1024 20 | 21 | func exampleError(_ error: String, location: String = "\(#file):\(#line)") -> NSError { 22 | return NSError(domain: "ExampleError", code: -1, userInfo: [NSLocalizedDescriptionKey: "\(location): \(error)"]) 23 | } 24 | 25 | extension String { 26 | func toFloat() -> Float? { 27 | let numberFormatter = NumberFormatter() 28 | return numberFormatter.number(from: self)?.floatValue 29 | } 30 | 31 | func toDouble() -> Double? { 32 | let numberFormatter = NumberFormatter() 33 | return numberFormatter.number(from: self)?.doubleValue 34 | } 35 | } 36 | 37 | func showAlert(_ message: String) { 38 | #if os(iOS) 39 | UIAlertView(title: "RxExample", message: message, delegate: nil, cancelButtonTitle: "OK").show() 40 | #elseif os(macOS) 41 | let alert = NSAlert() 42 | alert.messageText = message 43 | alert.runModal() 44 | #endif 45 | } 46 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | GitHubSearchRepositories 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Services/DownloadableImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownloadableImage.swift 3 | // RxExample 4 | // 5 | // Created by Vodovozov Gleb on 10/31/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | #if !RX_NO_MODULE 10 | import RxSwift 11 | #endif 12 | #if os(iOS) 13 | import UIKit 14 | #elseif os(macOS) 15 | import Cocoa 16 | #endif 17 | 18 | enum DownloadableImage{ 19 | case content(image:Image) 20 | case offlinePlaceholder 21 | 22 | } 23 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Services/HtmlParsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HtmlParsing.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 3/28/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | import class Foundation.NSString 10 | import class Foundation.NSRegularExpression 11 | import func Foundation.NSMakeRange 12 | import struct Foundation.URL 13 | 14 | func parseImageURLsfromHTML(_ html: NSString) throws -> [URL] { 15 | let regularExpression = try NSRegularExpression(pattern: "]*src=\"([^\"]+)\"[^>]*>", options: []) 16 | 17 | let matches = regularExpression.matches(in: html as String, options: [], range: NSMakeRange(0, html.length)) 18 | 19 | return matches.map { match -> URL? in 20 | if match.numberOfRanges != 2 { 21 | return nil 22 | } 23 | 24 | let url = html.substring(with: match.rangeAt(1)) 25 | 26 | var absoluteURLString = url 27 | if url.hasPrefix("//") { 28 | absoluteURLString = "http:" + url 29 | } 30 | 31 | return URL(string: absoluteURLString) 32 | }.filter { $0 != nil }.map { $0! } 33 | } 34 | 35 | func parseImageURLsfromHTMLSuitableForDisplay(_ html: NSString) throws -> [URL] { 36 | return try parseImageURLsfromHTML(html).filter { 37 | return $0.absoluteString.range(of: ".svg.") == nil 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Services/PseudoRandomGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PseudoRandomGenerator.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 6/28/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | 10 | 11 | // https://en.wikipedia.org/wiki/Random_number_generation 12 | class PseudoRandomGenerator { 13 | var m_w: UInt32 /* must not be zero, nor 0x464fffff */ 14 | var m_z: UInt32 /* must not be zero, nor 0x9068ffff */ 15 | 16 | init(_ m_w: UInt32, _ m_z: UInt32) { 17 | self.m_w = m_w 18 | self.m_z = m_z 19 | } 20 | 21 | func get_random() -> Int { 22 | m_z = 36969 &* (m_z & 65535) &+ (m_z >> 16); 23 | m_w = 18000 &* (m_w & 65535) &+ (m_w >> 16); 24 | let val = ((m_z << 16) &+ m_w) 25 | return Int(val % (1 << 30)) /* 32-bit result */ 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Services/UIImage+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Extensions.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 11/1/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #endif 12 | 13 | extension Image { 14 | func forceLazyImageDecompression() -> Image { 15 | #if os(iOS) 16 | UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) 17 | self.draw(at: CGPoint.zero) 18 | UIGraphicsEndImageContext() 19 | #endif 20 | return self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/Services/UIImageView+DownloadableImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+DownloadableImage.swift 3 | // RxExample 4 | // 5 | // Created by Vodovozov Gleb on 11/1/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | #if !RX_NO_MODULE 11 | import RxSwift 12 | import RxCocoa 13 | #endif 14 | import UIKit 15 | 16 | extension Reactive where Base: UIImageView { 17 | 18 | var downloadableImage: UIBindingObserver{ 19 | return downloadableImageAnimated(nil) 20 | } 21 | 22 | func downloadableImageAnimated(_ transitionType:String?) -> UIBindingObserver { 23 | return UIBindingObserver(UIElement: base) { imageView, image in 24 | for subview in imageView.subviews { 25 | subview.removeFromSuperview() 26 | } 27 | switch image { 28 | case .content(let image): 29 | (imageView as UIImageView).rx.image.on(.next(image)) 30 | case .offlinePlaceholder: 31 | let label = UILabel(frame: imageView.bounds) 32 | label.textAlignment = .center 33 | label.font = UIFont.systemFont(ofSize: 35) 34 | label.text = "⚠️" 35 | imageView.addSubview(label) 36 | } 37 | } 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/String+URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+URL.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 12/28/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | 10 | extension String { 11 | var URLEscaped: String { 12 | return self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GitHubSearchRepositoriesX/UINavigationController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+Extensions.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 12/13/15. 6 | // Copyright © 2015 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | #if !RX_NO_MODULE 11 | import RxSwift 12 | import RxCocoa 13 | #endif 14 | 15 | struct Colors { 16 | static let OfflineColor = UIColor(red: 1.0, green: 0.6, blue: 0.6, alpha: 1.0) 17 | static let OnlineColor = nil as UIColor? 18 | } 19 | 20 | extension Reactive where Base: UINavigationController { 21 | var serviceState: UIBindingObserver { 22 | return UIBindingObserver(UIElement: base) { navigationController, maybeServiceState in 23 | // if nil is being bound, then don't change color, it's not perfect, but :) 24 | if let serviceState = maybeServiceState { 25 | let isOffline = serviceState == .offline 26 | 27 | navigationController.navigationBar.backgroundColor = isOffline 28 | ? Colors.OfflineColor 29 | : Colors.OnlineColor 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /HandleError/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HandleError 4 | // 5 | // Created by wc on 27/12/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | } 18 | -------------------------------------------------------------------------------- /HandleError/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /HandleError/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /HandleError/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | 需要相机权限扫描二维码 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /HandleError/QRScan/QRScanResultViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRScanResultViewController.swift 3 | // HandleError 4 | // 5 | // Created by wc on 28/12/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class QRScanResultViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 DianQK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LocalAuthenticationDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LocalAuthenticationDemo 4 | // 5 | // Created by DianQK on 28/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /LocalAuthenticationDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /LocalAuthenticationDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LocalAuthenticationDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LocalAuthenticationDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /LocalAuthenticationDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // LocalAuthenticationDemo 4 | // 5 | // Created by DianQK on 28/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /PDFExpertContents/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PDF-Expert-Contents 4 | // 5 | // Created by DianQK on 17/09/2016. 6 | // Copyright © 2016 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /PDFExpertContents/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /PDFExpertContents/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PDFExpertContents/Assets.xcassets/arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /PDFExpertContents/Assets.xcassets/arrow.imageset/arrow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/PDFExpertContents/Assets.xcassets/arrow.imageset/arrow.pdf -------------------------------------------------------------------------------- /PDFExpertContents/ContentItemModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentItemModel.swift 3 | // PDF-Expert-Contents 4 | // 5 | // Created by DianQK on 24/09/2016. 6 | // Copyright © 2016 DianQK. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxDataSources 11 | import SwiftyJSON 12 | import RxExtensions 13 | 14 | typealias ExpandableContentItemModel = ExpandableItem 15 | 16 | struct ContentItemModel: IDHashable, IdentifiableType { 17 | 18 | let id: Int64 19 | let title: String 20 | let level: Int 21 | let url: URL? 22 | 23 | init(title: String, level: Int, id: Int64, url: URL?) { 24 | self.title = title 25 | self.level = level 26 | self.id = id 27 | self.url = url 28 | } 29 | 30 | var hashValue: Int { 31 | return id.hashValue 32 | } 33 | 34 | var identity: Int64 { 35 | return id 36 | } 37 | 38 | static func createExpandableContentItemModel(json: JSON, withPreLevel preLevel: Int) -> ExpandableContentItemModel { 39 | let title = json["title"].stringValue 40 | let id = json["id"].int64Value 41 | let url = URL(string: json["url"].stringValue) 42 | 43 | let level = preLevel + 1 44 | 45 | let subItems: [ExpandableContentItemModel] 46 | 47 | if let subJSON = json["subdirectory"].array, !subJSON.isEmpty { 48 | subItems = subJSON.map { createExpandableContentItemModel(json: $0, withPreLevel: level) } 49 | } else { 50 | subItems = [] 51 | } 52 | let contentItemModel = ContentItemModel(title: title, level: level, id: id, url: url) 53 | let expandableItem = ExpandableItem(model: contentItemModel, isExpanded: false, subItems: subItems) 54 | return expandableItem 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PDFExpertContents/ExpandedTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandedTableViewCell.swift 3 | // PDF-Expert-Contents 4 | // 5 | // Created by DianQK on 17/09/2016. 6 | // Copyright © 2016 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxExtensions 13 | 14 | class ExpandedTableViewCell: ReactiveTableViewCell { 15 | 16 | @IBOutlet private weak var expandMarkImageView: UIImageView! 17 | @IBOutlet private weak var titleLabel: UILabel! 18 | 19 | var attributedText: NSAttributedString? { 20 | get { 21 | return titleLabel?.attributedText 22 | } 23 | set(attributedText) { 24 | titleLabel.attributedText = attributedText 25 | } 26 | } 27 | 28 | var level: Int = 0 { 29 | didSet { 30 | let left = CGFloat(level * 15) + 15 + 15 + 10 31 | separatorInset = UIEdgeInsets(top: 0, left: left, bottom: 0, right: 0) 32 | } 33 | } 34 | 35 | var isExpanded: Bool = false { 36 | didSet { 37 | guard canExpanded, isExpanded != oldValue else { return } 38 | var from = CATransform3DIdentity 39 | var to = CATransform3DRotate(from, CGFloat(M_PI_2), 0, 0, 1) 40 | 41 | if !isExpanded { 42 | (from, to) = (to, from) 43 | } 44 | 45 | expandMarkImageView.layer.transform = to 46 | let affineTransformAnimation = CABasicAnimation(keyPath: "transform") 47 | affineTransformAnimation.fromValue = NSValue(caTransform3D: from) 48 | affineTransformAnimation.toValue = NSValue(caTransform3D: to) 49 | affineTransformAnimation.duration = 0.3 50 | expandMarkImageView.layer.add(affineTransformAnimation, forKey: nil) 51 | } 52 | } 53 | 54 | var canExpanded: Bool = false { 55 | didSet { 56 | expandMarkImageView.isHidden = !canExpanded 57 | } 58 | } 59 | 60 | } 61 | 62 | extension Reactive where Base: ExpandedTableViewCell { 63 | var isExpanded: AnyObserver { 64 | return UIBindingObserver(UIElement: self.base as ExpandedTableViewCell, binding: { (cell, isExpanded) in 65 | if cell.isExpanded != isExpanded { 66 | cell.isExpanded = isExpanded 67 | } 68 | }).asObserver() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /PDFExpertContents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Questions/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Questions 4 | // 5 | // Created by DianQK on 10/11/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Questions/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Questions/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Questions/Assets.xcassets/dianqk.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "DianQK@200.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Questions/Assets.xcassets/dianqk.imageset/DianQK@200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/Questions/Assets.xcassets/dianqk.imageset/DianQK@200.png -------------------------------------------------------------------------------- /Questions/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Questions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Questions/OptionCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionCollectionViewCell.swift 3 | // Questions 4 | // 5 | // Created by DianQK on 10/11/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxDataSources 13 | import RxExtensions 14 | 15 | class OptionCollectionViewCell: ReactiveCollectionViewCell { 16 | 17 | @IBOutlet weak var displayImageView: UIImageView! 18 | 19 | override func prepareForReuse() { 20 | super.prepareForReuse() 21 | contentView.alpha = 1 22 | } 23 | 24 | } 25 | 26 | extension Reactive where Base: OptionCollectionViewCell { 27 | 28 | var isSelected: UIBindingObserver { 29 | return UIBindingObserver(UIElement: self.base) { cell, isSelected in 30 | cell.contentView.alpha = isSelected ? 1 : 0.5 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Questions/QuestionCollectionReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuestionCollectionReusableView.swift 3 | // Questions 4 | // 5 | // Created by DianQK on 10/11/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class QuestionCollectionReusableView: UICollectionReusableView { 12 | 13 | @IBOutlet weak var questionLabel: UILabel! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rx-sample-code 2 | 3 | ## Demo 列表 4 | 5 | ### [Stopwatch](http://7xokf3.com1.z0.glb.clouddn.com/Stopwatch.mov) 6 | 7 | 仿 Apple 计时器逻辑,使用了 MVVM 和状态机,代码仅 200 行。 8 | 9 | ### [PDF-Expert-Contents](http://7xokf3.com1.z0.glb.clouddn.com/pdf-epert-demo-mute.mov) 10 | 11 | 仿 PDF Expert 目录展开逻辑,支持无限层级展开。 12 | 13 | ### [Expandable](http://7xokf3.com1.z0.glb.clouddn.com/expanded-sample.mov) 14 | 15 | 对 [如何在 iOS 中实现一个可展开的 Table View](http://swift.gg/2015/12/03/expandable-table-view/) 一文的 Demo ,用 Rx 重写。 16 | 17 | ### RxDataSourcesExample 18 | 19 | RxDataSources 基本使用例子。 20 | 21 | ### SelectCell 22 | 23 | 更新 Cell 选择状态例子,如选择联系人(单选/多选)。 24 | 25 | > 这是一种单选的方案,另外一种可以参见 TwoWayBind 中的 SelectPayment ,根据具体情况选择使用哪种方案。个人推荐本例中的选择联系人方案。 26 | 27 | ### TwoWayBind 28 | 29 | 内含: 30 | 31 | - 加减购物车 32 | - 选择支付方式 33 | - 更改带组分类的推送设置 34 | 35 | ## 如何运行 36 | 37 | 项目依赖使用 Carthage 管理。 38 | 39 | 参考命令: 40 | 41 | ``` 42 | carthage update --verbose --platform ios --color auto --no-use-binaries 43 | ``` 44 | 45 | > 期待其他 Demo 或者对代码有什么疑问或者建议,欢迎提 issue ,我会尽快回复。 46 | 47 | ## License 48 | 49 | MIT 50 | -------------------------------------------------------------------------------- /RxDataSourcesExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // rx-sample-code 4 | // 5 | // Created by DianQK on 01/02/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/DianQK.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "DianQK@200.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "DianQK@400.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/DianQK.imageset/DianQK@200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/RxDataSourcesExample/Assets.xcassets/DianQK.imageset/DianQK@200.png -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/DianQK.imageset/DianQK@400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/RxDataSourcesExample/Assets.xcassets/DianQK.imageset/DianQK@400.png -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/btn_add.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_add.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/btn_add.imageset/btn_add.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/RxDataSourcesExample/Assets.xcassets/btn_add.imageset/btn_add.pdf -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/btn_delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_delete.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/btn_delete.imageset/btn_delete.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/RxDataSourcesExample/Assets.xcassets/btn_delete.imageset/btn_delete.pdf -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/btn_delete_press.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_delete_press.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /RxDataSourcesExample/Assets.xcassets/btn_delete_press.imageset/btn_delete_press.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/RxDataSourcesExample/Assets.xcassets/btn_delete_press.imageset/btn_delete_press.pdf -------------------------------------------------------------------------------- /RxDataSourcesExample/CellIdentifierTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellIdentifierTableViewController.swift 3 | // RxDataSourcesExample 4 | // 5 | // Created by DianQK on 03/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxExtensions 13 | 14 | private struct Option { 15 | let title: String 16 | let isOn: Bool 17 | init(title: String, isOn: Bool) { 18 | self.title = title 19 | self.isOn = isOn 20 | } 21 | } 22 | 23 | /// 5_1_2 24 | class CellIdentifierTableViewController: UITableViewController { 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | tableView.dataSource = nil 30 | tableView.delegate = nil 31 | 32 | let items = Observable.just([ 33 | Option(title: "选项一", isOn: true), 34 | Option(title: "选项二", isOn: false), 35 | Option(title: "选项三", isOn: true), 36 | ]) 37 | 38 | items 39 | .bindTo(tableView.rx.items(cellIdentifier: "TipTableViewCell", cellType: TipTableViewCell.self)) { (row, element, cell) in 40 | cell.title = element.title 41 | cell.isOn = element.isOn 42 | } 43 | .disposed(by: rx.disposeBag) 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /RxDataSourcesExample/CellIdentifierViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellIdentifierViewController.swift 3 | // RxDataSourcesExample 4 | // 5 | // Created by DianQK on 03/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxExtensions 13 | /// 5_1_1 14 | class CellIdentifierViewController: UIViewController { 15 | 16 | @IBOutlet private weak var tableView: UITableView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | let items = Observable.just([ 22 | "First Item", 23 | "Second Item", 24 | "Third Item" 25 | ]) 26 | 27 | items 28 | .bindTo(tableView.rx.items(cellIdentifier: "BasicCell", cellType: UITableViewCell.self)) { (row, element, cell) in 29 | cell.textLabel?.text = "\(element) @ row \(row)" 30 | } 31 | .disposed(by: rx.disposeBag) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Collection/AutomaticCollectionViewFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutomaticCollectionViewFlowLayout.swift 3 | // RxDataSourcesExample 4 | // 5 | // Created by DianQK on 02/11/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AutomaticCollectionViewFlowLayout: UICollectionViewFlowLayout { 12 | 13 | private var _indexPathsToAnimate: [IndexPath] = [] 14 | 15 | override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { 16 | super.prepare(forCollectionViewUpdates: updateItems) 17 | 18 | _indexPathsToAnimate = updateItems 19 | .flatMap { updateItem -> IndexPath? in 20 | switch updateItem.updateAction { 21 | case .insert: 22 | return updateItem.indexPathAfterUpdate 23 | case .delete: 24 | return updateItem.indexPathBeforeUpdate 25 | case .none, .reload, .move: 26 | return nil 27 | } 28 | } 29 | } 30 | 31 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 32 | return false 33 | } 34 | 35 | override func finalizeCollectionViewUpdates() { 36 | super.finalizeCollectionViewUpdates() 37 | _indexPathsToAnimate = [] 38 | } 39 | 40 | 41 | override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 42 | 43 | guard let attr = layoutAttributesForItem(at: itemIndexPath)?.copy() as? UICollectionViewLayoutAttributes else { 44 | return nil 45 | } 46 | 47 | if let index = _indexPathsToAnimate.index(of: itemIndexPath) { 48 | attr.transform = CGAffineTransform(scaleX: 0.3, y: 0.3) 49 | attr.alpha = 0.3 50 | _indexPathsToAnimate.remove(at: index) 51 | } 52 | 53 | return attr 54 | } 55 | 56 | override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 57 | if let index = _indexPathsToAnimate.index(of: itemIndexPath) { 58 | _indexPathsToAnimate.remove(at: index) 59 | return super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath) 60 | } else { 61 | return layoutAttributesForItem(at: itemIndexPath) 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Collection/HUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUD.swift 3 | // RxDataSourcesExample 4 | // 5 | // Created by DianQK on 03/11/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MBProgressHUD 11 | 12 | class HUD { 13 | 14 | private init() { } 15 | /** 16 | 显示一个提示消息 17 | 18 | - parameter message: 显示内容 19 | */ 20 | static func showMessage(_ message: String) { 21 | let hud = MBProgressHUD.showAdded(to: UIApplication.shared.keyWindow!, animated: true) 22 | 23 | hud.mode = MBProgressHUDMode.text 24 | hud.label.text = message 25 | hud.margin = 10 26 | hud.offset.y = 150 27 | hud.removeFromSuperViewOnHide = true 28 | hud.isUserInteractionEnabled = false 29 | 30 | hud.hide(animated: true, afterDelay: 1) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/Cells/Cell+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cell+Rx.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/17/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | private var _prepareForReuseBag: Void = () 14 | 15 | extension UITableViewCell { 16 | fileprivate var rx_prepareForReuse: Observable { 17 | return Observable.of(self.rx.sentMessage(#selector(UITableViewCell.prepareForReuse)).map { _ in () }, self.rx.deallocated).merge() 18 | } 19 | 20 | fileprivate var rx_prepareForReuseBag: DisposeBag { 21 | MainScheduler.ensureExecutingOnScheduler() 22 | 23 | if let bag = objc_getAssociatedObject(self, &_prepareForReuseBag) as? DisposeBag { 24 | return bag 25 | } 26 | 27 | let bag = DisposeBag() 28 | objc_setAssociatedObject(self, &_prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 29 | 30 | _ = self.rx.sentMessage(#selector(UITableViewCell.prepareForReuse)) 31 | .subscribe(onNext: { [weak self] _ in 32 | let newBag = DisposeBag() 33 | objc_setAssociatedObject(self, &_prepareForReuseBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 34 | }) 35 | 36 | return bag 37 | } 38 | } 39 | 40 | extension Reactive where Base: UITableViewCell { 41 | var prepareForReuse: Observable { 42 | return Observable.of((base as UITableViewCell).rx.sentMessage(#selector(UITableViewCell.prepareForReuse)).map { _ in }, (base as UITableViewCell).rx.deallocated).merge() 43 | } 44 | 45 | var prepareForReuseBag: DisposeBag { 46 | return base.rx_prepareForReuseBag 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/Cells/DatePickerCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatePickerCell.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/17/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class DatePickerCell: UITableViewCell { 14 | 15 | @IBOutlet fileprivate weak var datePicker: UIDatePicker! 16 | 17 | } 18 | 19 | extension Reactive where Base: DatePickerCell { 20 | 21 | var date: ControlProperty { 22 | return base.datePicker.rx.date 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/Cells/SliderCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SliderCell.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/17/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class SliderCell: UITableViewCell { 14 | 15 | @IBOutlet weak var slider: UISlider! 16 | 17 | } 18 | extension Reactive where Base: SliderCell { 19 | var value: ControlProperty { 20 | let observer = UIBindingObserver(UIElement: base.slider) { (slider, value) in 21 | slider.value = Float(value) 22 | }.asObserver() 23 | return ControlProperty(values: base.slider.rx.value.map(Int.init), valueSink: observer) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/Cells/SwitchCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCell.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/17/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class SwitchCell: UITableViewCell { 14 | 15 | @IBOutlet private weak var titleLabel: UILabel! 16 | @IBOutlet fileprivate weak var switchView: UISwitch! 17 | 18 | var title: String? { 19 | get { 20 | return titleLabel?.text 21 | } 22 | set { 23 | titleLabel?.text = newValue 24 | } 25 | } 26 | 27 | } 28 | 29 | extension Reactive where Base: SwitchCell { 30 | var isOn: ControlProperty { 31 | return base.switchView.rx.value 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/Cells/TextFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCell.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/17/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class TextFieldCell: UITableViewCell { 14 | 15 | @IBOutlet fileprivate weak var textField: UITextField! 16 | 17 | var placeholder: String? { 18 | get { 19 | return textField?.placeholder 20 | } 21 | set { 22 | textField?.placeholder = newValue 23 | } 24 | } 25 | 26 | } 27 | 28 | extension Reactive where Base: TextFieldCell { 29 | var text: TextInput { 30 | return base.textField.rx.textInput 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/18/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Config { 12 | let view: View 13 | init(_ view: View) { 14 | self.view = view 15 | } 16 | } 17 | 18 | extension NSObjectProtocol { 19 | var config: Config { 20 | return Config(self) 21 | } 22 | } 23 | 24 | extension Config where View: DateFormatter { 25 | var longStyle: Config { 26 | view.dateStyle = DateFormatter.Style.long 27 | return self 28 | } 29 | 30 | var string: (Date) -> String { 31 | return view.string 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Expandable/SafeCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafeCollection.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/18/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct SafeCollection { //: Collection { 12 | /// Returns a subsequence containing all but the given number of initial 13 | /// elements. 14 | /// 15 | /// If the number of elements to drop exceeds the number of elements in 16 | /// the sequence, the result is an empty subsequence. 17 | /// 18 | /// let numbers = [1, 2, 3, 4, 5] 19 | /// print(numbers.dropFirst(2)) 20 | /// // Prints "[3, 4, 5]" 21 | /// print(numbers.dropFirst(10)) 22 | /// // Prints "[]" 23 | /// 24 | /// - Parameter n: The number of elements to drop from the beginning of 25 | /// the sequence. `n` must be greater than or equal to zero. 26 | /// - Returns: A subsequence starting after the specified number of 27 | /// elements. 28 | /// 29 | /// - Complexity: O(*n*), where *n* is the number of elements to drop from 30 | /// the beginning of the sequence. 31 | public typealias SubSequence = Base.SubSequence 32 | public func dropFirst(_ n: Int) -> SubSequence { 33 | return _base.dropFirst(n) 34 | } 35 | 36 | private var _base: Base 37 | public init(_ base: Base) { 38 | _base = base 39 | } 40 | 41 | public typealias Index = Base.Index 42 | public var startIndex: Index { 43 | return _base.startIndex 44 | } 45 | 46 | public var endIndex: Index { 47 | return _base.endIndex 48 | } 49 | 50 | public subscript(index: Base.Index) -> Base.Iterator.Element? { 51 | if startIndex <= index && index < endIndex { 52 | return _base[index] 53 | } 54 | return nil 55 | } 56 | 57 | public subscript(bounds: Range) -> Base.SubSequence? { 58 | if startIndex <= bounds.lowerBound && bounds.upperBound < endIndex { 59 | return _base[bounds] 60 | } 61 | return nil 62 | } 63 | 64 | var safe: SafeCollection { //Allows to chain ".safe" without side effects 65 | return self 66 | } 67 | } 68 | 69 | public extension Collection { 70 | var safe: SafeCollection { 71 | return SafeCollection(self) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /RxDataSourcesExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /RxDataSourcesExample/SectionTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionTableViewController.swift 3 | // RxDataSourcesExample 4 | // 5 | // Created by DianQK on 03/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxExtensions 13 | import RxDataSources 14 | /// 5_1_3 15 | typealias TitleSectionModel = SectionModel 16 | 17 | class SectionTableViewController: UITableViewController { 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | tableView.dataSource = nil 23 | tableView.delegate = nil 24 | 25 | let dataSource = RxTableViewSectionedReloadDataSource() 26 | 27 | dataSource.configureCell = { dataSource, tableView, indexPath, item in 28 | let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.basicCell, for: indexPath)! 29 | cell.textLabel?.text = item 30 | return cell 31 | } 32 | 33 | dataSource.titleForHeaderInSection = { dataSource, section in 34 | return dataSource[section].model 35 | } 36 | 37 | let sections = Observable.just([ 38 | TitleSectionModel(model: "Section 1", items: ["Item 1", "Item 2", "Item 3"]), 39 | TitleSectionModel(model: "Section 2", items: ["Item 1", "Item 2"]), 40 | TitleSectionModel(model: "Section 3", items: ["Item 1", "Item 2", "Item 3", "Item 4"]), 41 | ]) 42 | 43 | sections 44 | .bindTo(tableView.rx.items(dataSource: dataSource)) 45 | .disposed(by: rx.disposeBag) 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /RxDataSourcesExample/TipTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TipTableViewCell.swift 3 | // RxDataSourcesExample 4 | // 5 | // Created by DianQK on 03/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class TipTableViewCell: UITableViewCell { 14 | 15 | @IBOutlet fileprivate weak var titleLabel: UILabel! 16 | @IBOutlet fileprivate weak var switchView: UISwitch! 17 | 18 | private(set) var disposeBag = DisposeBag() 19 | 20 | override func prepareForReuse() { 21 | super.prepareForReuse() 22 | disposeBag = DisposeBag() 23 | } 24 | 25 | var isOn: Bool { 26 | get { 27 | return switchView.isOn 28 | } 29 | set(isOn) { 30 | switchView.isOn = isOn 31 | } 32 | } 33 | 34 | var title: String? { 35 | get { 36 | return titleLabel.text 37 | } 38 | set(title) { 39 | titleLabel.text = title 40 | } 41 | } 42 | } 43 | 44 | extension Reactive where Base: TipTableViewCell { 45 | var isOn: ControlProperty { 46 | return base.switchView.rx.value 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SelectCell/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SelectCell 4 | // 5 | // Created by DianQK on 19/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SelectCell/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SelectCell/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SelectCell/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SelectCell/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SelectCell/MultiSelectTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiSelectTableViewController.swift 3 | // SelectCell 4 | // 5 | // Created by DianQK on 19/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxDataSources 11 | import RxExtensions 12 | 13 | class MultiSelectTableViewController: UITableViewController { 14 | 15 | @IBOutlet private weak var doneBarButtonItem: UIBarButtonItem! 16 | 17 | private let dataSource = RxTableViewSectionedReloadDataSource() 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | skinTableViewDataSource(dataSource) 23 | 24 | do { 25 | tableView.rx.modelSelected(User.self) 26 | .subscribe(onNext: { user in 27 | user.isSelected.value = !user.isSelected.value 28 | }) 29 | .addDisposableTo(rx.disposeBag) 30 | 31 | tableView.rx.enableAutoDeselect().addDisposableTo(rx.disposeBag) 32 | } 33 | 34 | do { 35 | let users = getUsers().shareReplay(1) 36 | 37 | users 38 | .map(convertUsersToSections) 39 | .bindTo(tableView.rx.items(dataSource: dataSource)) 40 | .addDisposableTo(rx.disposeBag) 41 | 42 | doneBarButtonItem.rx.tap.withLatestFrom(users) 43 | .map(combineSelectedUsersInfo) 44 | .flatMap { message in 45 | return showAlert(title: "您选择了", message: message) 46 | } 47 | .subscribe(onNext: pop) 48 | .addDisposableTo(rx.disposeBag) 49 | 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /SelectCell/RadioTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadioTableViewController.swift 3 | // SelectCell 4 | // 5 | // Created by DianQK on 20/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxDataSources 11 | import RxExtensions 12 | 13 | class RadioTableViewController: UITableViewController { 14 | 15 | @IBOutlet private weak var doneBarButtonItem: UIBarButtonItem! 16 | private let dataSource = RxTableViewSectionedReloadDataSource() 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | skinTableViewDataSource(dataSource) 22 | 23 | do { 24 | tableView.rx.modelSelected(User.self) 25 | .distinctUntilChanged({ (pre, user) -> Bool in 26 | guard pre.id != user.id else { return true } // 选择同一个,什么都不做 27 | pre.isSelected.reverse() // 切换上一个的选择状态 28 | return false 29 | }) 30 | .subscribe(onNext: { (user) in 31 | user.isSelected.reverse() 32 | }) 33 | .addDisposableTo(rx.disposeBag) 34 | 35 | tableView.rx.enableAutoDeselect().addDisposableTo(rx.disposeBag) 36 | } 37 | 38 | do { 39 | let users = getUsers().shareReplay(1) 40 | 41 | users 42 | .map(convertUsersToSections) 43 | .bindTo(tableView.rx.items(dataSource: dataSource)) 44 | .addDisposableTo(rx.disposeBag) 45 | 46 | doneBarButtonItem.rx.tap.withLatestFrom(users) 47 | .map(combineSelectedUsersInfo) 48 | .flatMap { message in 49 | return showAlert(title: "您选择了", message: message) 50 | } 51 | .subscribe(onNext: pop) 52 | .addDisposableTo(rx.disposeBag) 53 | } 54 | } 55 | 56 | deinit { 57 | print("deinit \(self)") 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /SelectCell/SkinDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SkinDataSource.swift 3 | // SelectCell 4 | // 5 | // Created by DianQK on 20/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import RxDataSources 10 | 11 | func skinTableViewDataSource(_ dataSource: RxTableViewSectionedReloadDataSource) { 12 | dataSource.configureCell = { dataSource, tableView, indexPath, element in 13 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! UserTableViewCell 14 | cell.textLabel?.text = element.name 15 | element.isSelected.asObservable() 16 | .bindTo(cell.rx.isMarked) 17 | .addDisposableTo(cell.prepareForReuseBag) 18 | return cell 19 | } 20 | 21 | dataSource.titleForHeaderInSection = { dataSource, section in 22 | return dataSource.sectionModels[section].model 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SelectCell/UserTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserTableViewCell.swift 3 | // SelectCell 4 | // 5 | // Created by DianQK on 19/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | import RxCocoa 11 | import RxExtensions 12 | 13 | class UserTableViewCell: ReactiveTableViewCell { } 14 | 15 | extension Reactive where Base: UserTableViewCell { 16 | var isMarked: UIBindingObserver { 17 | return UIBindingObserver(UIElement: self.base, binding: { (cell, isMarked) in 18 | cell.accessoryType = isMarked ? .checkmark : .none 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SelectCell/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SelectCell 4 | // 5 | // Created by DianQK on 19/10/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Stopwatch/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 9/8/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/gray.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "gray.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/gray.imageset/gray.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/Stopwatch/Assets.xcassets/gray.imageset/gray.pdf -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "green.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/green.imageset/green.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/Stopwatch/Assets.xcassets/green.imageset/green.pdf -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "red.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Stopwatch/Assets.xcassets/red.imageset/red.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/Stopwatch/Assets.xcassets/red.imageset/red.pdf -------------------------------------------------------------------------------- /Stopwatch/BasicViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicViewModel.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 12/09/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | //import RxExtensions 13 | 14 | struct BasicViewModel: StopwatchViewModelProtocol { 15 | 16 | let startAStopStyle: Observable 17 | let resetALapStyle: Observable 18 | let displayTime: Observable 19 | let displayElements: Observable<[(title: Observable, displayTime: Observable, color: Observable)]> 20 | 21 | private enum State { 22 | case timing, stopped 23 | } 24 | 25 | init(input: (startAStopTrigger: Observable, resetALapTrigger: Observable)) { 26 | 27 | let state = input.startAStopTrigger 28 | .scan(State.stopped) { 29 | switch $0.0 { 30 | case .stopped: return State.timing 31 | case .timing: return State.stopped 32 | } 33 | } 34 | .shareReplay(1) 35 | 36 | displayTime = state 37 | .flatMapLatest { state -> Observable in 38 | switch state { 39 | case .stopped: 40 | return Observable.empty() 41 | case .timing: 42 | return Observable.interval(0.01, scheduler: MainScheduler.instance).map { _ in 0.01 } 43 | } 44 | } 45 | .scan(0, accumulator: +) 46 | .startWith(0) 47 | .map(Tool.convertToTimeInfo) 48 | 49 | startAStopStyle = state 50 | .map { state in 51 | switch state { 52 | case .stopped: 53 | return Style.Button(title: "Start", titleColor: Tool.Color.green, isEnabled: true, backgroungImage: #imageLiteral(resourceName: "green")) 54 | case .timing: 55 | return Style.Button(title: "Stop", titleColor: Tool.Color.red, isEnabled: true, backgroungImage: #imageLiteral(resourceName: "red")) 56 | } 57 | } 58 | resetALapStyle = Observable.just(Style.Button(title: "", titleColor: UIColor.white, isEnabled: false, backgroungImage: #imageLiteral(resourceName: "gray"))) 59 | displayElements = Observable.empty() 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Stopwatch/Button+Style.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Button+Style.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 10/09/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | struct Style { 14 | struct Button { 15 | let title: String 16 | let titleColor: UIColor 17 | let isEnabled: Bool 18 | let backgroungImage: UIImage? 19 | } 20 | } 21 | 22 | extension Reactive where Base: UIButton { 23 | var style: AnyObserver { 24 | return UIBindingObserver(UIElement: self.base, binding: { (button, style) in 25 | button.setTitle(style.title, for: .normal) 26 | button.setTitleColor(style.titleColor, for: .normal) 27 | button.isEnabled = style.isEnabled 28 | button.setBackgroundImage(style.backgroungImage, for: .normal) 29 | }).asObserver() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Stopwatch/Cell+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cell+Rx.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/17/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | //import UIKit 10 | //import RxSwift 11 | //import RxCocoa 12 | 13 | //extension Reactive where Base: UITableViewCell { 14 | // public var prepareForReuse: Observable { 15 | // return Observable.of((base as UITableViewCell).rx.sentMessage(#selector(UITableViewCell.prepareForReuse)).map { _ in }, (base as UITableViewCell).rx.deallocated).merge() 16 | // } 17 | // 18 | // public var prepareForReuseBag: DisposeBag { 19 | // return base._rx_prepareForReuseBag 20 | // } 21 | //} 22 | 23 | //extension UITableViewCell { 24 | // 25 | // private struct AssociatedKeys { 26 | // static var _disposeBag: Void = () 27 | // } 28 | // 29 | // fileprivate var _rx_prepareForReuse: Observable { 30 | // return Observable.of(self.rx.sentMessage(#selector(UITableViewCell.prepareForReuse)).map { _ in () }, self.rx.deallocated).merge() 31 | // } 32 | // 33 | // fileprivate var _rx_prepareForReuseBag: DisposeBag { 34 | // MainScheduler.ensureExecutingOnScheduler() 35 | // 36 | // if let bag = objc_getAssociatedObject(self, &AssociatedKeys._disposeBag) as? DisposeBag { 37 | // return bag 38 | // } 39 | // 40 | // let bag = DisposeBag() 41 | // objc_setAssociatedObject(self, &AssociatedKeys._disposeBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 42 | // 43 | // _ = self.rx.sentMessage(#selector(UITableViewCell.prepareForReuse)) 44 | // .subscribe(onNext: { [weak self] _ in 45 | // let newBag = DisposeBag() 46 | // objc_setAssociatedObject(self, &AssociatedKeys._disposeBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 47 | // }) 48 | // 49 | // return bag 50 | // } 51 | //} 52 | 53 | -------------------------------------------------------------------------------- /Stopwatch/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // RxAutomaton 4 | // 5 | // Created by Yasuhiro Inami on 2016-08-15. 6 | // Copyright © 2016 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import QuartzCore 10 | import RxSwift 11 | import RxCocoa 12 | 13 | // MARK: CoreAnimation 14 | 15 | extension CALayer 16 | { 17 | public var rx_position: AnyObserver { 18 | return UIBindingObserver(UIElement: self) { layer, value in 19 | layer.position = value 20 | }.asObserver() 21 | } 22 | 23 | public var rx_hidden: AnyObserver { 24 | return UIBindingObserver(UIElement: self) { layer, value in 25 | layer.isHidden = value 26 | }.asObserver() 27 | } 28 | 29 | public var rx_backgroundColor: AnyObserver { 30 | return UIBindingObserver(UIElement: self) { layer, value in 31 | layer.backgroundColor = value 32 | }.asObserver() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Stopwatch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Stopwatch/Input.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Input.swift 3 | // RxAutomaton 4 | // 5 | // Created by Yasuhiro Inami on 2016-08-15. 6 | // Copyright © 2016 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// - Note: 12 | /// `LoginOK` and `LogoutOK` should only be used internally 13 | /// (but Swift can't make them as `private case`) 14 | enum Input: String, CustomStringConvertible 15 | { 16 | case login 17 | case loginOK 18 | case logout 19 | case forceLogout 20 | case logoutOK 21 | 22 | var description: String { return self.rawValue } 23 | } 24 | -------------------------------------------------------------------------------- /Stopwatch/State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // State.swift 3 | // RxAutomaton 4 | // 5 | // Created by Yasuhiro Inami on 2016-08-15. 6 | // Copyright © 2016 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | enum State: String, CustomStringConvertible 10 | { 11 | case loggedOut 12 | case loggingIn 13 | case loggedIn 14 | case loggingOut 15 | 16 | var description: String { return self.rawValue } 17 | } 18 | -------------------------------------------------------------------------------- /Stopwatch/StopwatchTypeListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StopwatchTypeListViewController.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 12/09/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | typealias Action = () -> () 14 | 15 | class StopwatchTypeListViewController: UIViewController { 16 | 17 | let disposeBag = DisposeBag() 18 | 19 | private struct Item { 20 | let title: String 21 | let type: StopwatchViewModelProtocol.Type 22 | init(title: String, type: StopwatchViewModelProtocol.Type) { 23 | self.title = title 24 | self.type = type 25 | } 26 | } 27 | 28 | @IBOutlet private weak var listTableView: UITableView! 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | let items = Observable.just([ 34 | Item(title: "Basic", type: BasicViewModel.self), 35 | Item(title: "Timing", type: TimingViewModel.self), 36 | Item(title: "Final", type: FinalViewModel.self) 37 | ]) 38 | 39 | items 40 | .bindTo(listTableView.rx.items(cellIdentifier: "ListTableViewCell")) { index, element, cell in 41 | cell.textLabel?.text = element.title 42 | } 43 | .disposed(by: disposeBag) 44 | 45 | listTableView.rx.itemSelected.map { (at: $0, animated: true) } 46 | .subscribe(onNext: listTableView.deselectRow) 47 | .disposed(by: disposeBag) 48 | 49 | listTableView.rx.modelSelected(Item.self) 50 | .subscribe(onNext: { [unowned self] item in 51 | let stopwatchViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController 52 | stopwatchViewController.type = item.type 53 | self.show(stopwatchViewController, sender: nil) 54 | }) 55 | .disposed(by: disposeBag) 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Stopwatch/StopwatchViewModelProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StopwatchViewModelProtocol.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 12/09/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | 12 | protocol StopwatchViewModelProtocol { 13 | var startAStopStyle: Observable { get } 14 | var resetALapStyle: Observable { get } 15 | var displayTime: Observable { get } 16 | var displayElements: Observable<[(title: Observable, displayTime: Observable, color: Observable)]> { get } 17 | 18 | init(input: (startAStopTrigger: Observable, resetALapTrigger: Observable)) 19 | } 20 | -------------------------------------------------------------------------------- /Stopwatch/Tool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tool.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 12/09/2016. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Tool { 12 | static let convertToTimeInfo: (TimeInterval) -> String = { ms in 13 | var form = DateFormatter() 14 | form.dateFormat = "mm:ss.SS" 15 | let date = Date(timeIntervalSince1970: ms) 16 | return form.string(from: date) 17 | } 18 | 19 | struct Color { 20 | static let red = UIColor(red: 252.0 / 255.0, green: 61.0 / 255.0, blue: 57.0 / 255.0, alpha: 1) 21 | static let green = UIColor(red: 83.0 / 255.0, green: 215.0 / 255.0, blue: 105.0 / 255.0, alpha: 1) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Stopwatch/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Stopwatch 4 | // 5 | // Created by DianQK on 9/8/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxExtensions 12 | 13 | class ViewController: UIViewController { 14 | 15 | @IBOutlet private weak var displayTimeLabel: UILabel! 16 | @IBOutlet private weak var resetButton: UIButton! 17 | @IBOutlet private weak var startButton: UIButton! 18 | @IBOutlet private weak var lapsTableView: UITableView! 19 | 20 | var type: StopwatchViewModelProtocol.Type = FinalViewModel.self 21 | 22 | let disposeBag = DisposeBag() 23 | 24 | private lazy var viewModel: StopwatchViewModelProtocol = self.type.init(input: ( 25 | startAStopTrigger: self.startButton.rx.tap.asObservable(), 26 | resetALapTrigger: self.resetButton.rx.tap.asObservable()) 27 | ) 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | do { // MARK: 展示时间 33 | viewModel.displayTime 34 | .bindTo(displayTimeLabel.rx.text) 35 | .disposed(by: rx.disposeBag) 36 | } 37 | 38 | viewModel.resetALapStyle 39 | .bindTo(resetButton.rx.style) 40 | .disposed(by: disposeBag) 41 | viewModel.startAStopStyle 42 | .bindTo(startButton.rx.style) 43 | .disposed(by: disposeBag) 44 | 45 | viewModel.displayElements 46 | .bindTo(lapsTableView.rx.items(cellIdentifier: "LapTableViewCell")) { index, element, cell in 47 | guard let detailTextLabel = cell.detailTextLabel else { return } 48 | element.displayTime.bindTo(detailTextLabel.rx.text).disposed(by: cell.rx.prepareForReuseBag) 49 | element.color.bindTo(detailTextLabel.rx.textColor).disposed(by: cell.rx.prepareForReuseBag) 50 | } 51 | .disposed(by: disposeBag) 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /TextInput/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TextInputDemo 4 | // 5 | // Created by DianQK on 18/01/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /TextInput/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /TextInput/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TextInput/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TextInput/InputCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputCollectionViewCell.swift 3 | // TextInputDemo 4 | // 5 | // Created by DianQK on 18/01/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class InputCollectionViewCell: UICollectionViewCell { 14 | 15 | @IBOutlet weak var textField: InputTextField! 16 | 17 | var isInputing: UIBindingObserver { 18 | return UIBindingObserver(UIElement: self, binding: { (UIElement, value) in 19 | UIElement.textField._canBecomeFirstResponder = value 20 | if value { 21 | _ = UIElement.becomeFirstResponder() 22 | } else { 23 | _ = UIElement.resignFirstResponder() 24 | } 25 | }) 26 | } 27 | 28 | var canInput: UIBindingObserver { 29 | return UIBindingObserver(UIElement: self, binding: { (UIElement, value) in 30 | UIElement.textField._canBecomeFirstResponder = value 31 | }) 32 | } 33 | 34 | override func becomeFirstResponder() -> Bool { 35 | return textField.becomeFirstResponder() 36 | } 37 | 38 | override var canBecomeFirstResponder: Bool { 39 | return textField.canBecomeFirstResponder 40 | } 41 | 42 | override var canResignFirstResponder: Bool { 43 | return textField.canResignFirstResponder 44 | } 45 | 46 | override func resignFirstResponder() -> Bool { 47 | return textField.resignFirstResponder() 48 | } 49 | 50 | override var isFirstResponder: Bool { 51 | return textField.isFirstResponder 52 | } 53 | 54 | private(set) var reuseDisposeBag = DisposeBag() 55 | 56 | override func awakeFromNib() { 57 | super.awakeFromNib() 58 | textField.text = nil 59 | } 60 | 61 | override func prepareForReuse() { 62 | super.prepareForReuse() 63 | textField.text = nil 64 | reuseDisposeBag = DisposeBag() 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /TextInput/InputTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputTextField.swift 3 | // TextInputDemo 4 | // 5 | // Created by DianQK on 30/01/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class InputTextField: UITextField { 12 | 13 | override var canBecomeFirstResponder: Bool { 14 | return _canBecomeFirstResponder && super.canBecomeFirstResponder 15 | } 16 | 17 | var _canBecomeFirstResponder: Bool = true 18 | 19 | } 20 | -------------------------------------------------------------------------------- /TwoWayBind/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TwoWayBind 4 | // 5 | // Created by DianQK on 8/24/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/btn_minus_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_minus_normal.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/btn_minus_normal.imageset/btn_minus_normal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/btn_minus_normal.imageset/btn_minus_normal.pdf -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/btn_plus_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btn_plus_normal.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/btn_plus_normal.imageset/btn_plus_normal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/btn_plus_normal.imageset/btn_plus_normal.pdf -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/ic_right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_right.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/ic_right.imageset/ic_right.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/ic_right.imageset/ic_right.pdf -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/ic_select.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_select.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/ic_select.imageset/ic_select.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/ic_select.imageset/ic_select.pdf -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/ic_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_selected.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/ic_selected.imageset/ic_selected.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/ic_selected.imageset/ic_selected.pdf -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_alipay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "purchase_icon_alipay@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "purchase_icon_alipay@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_alipay.imageset/purchase_icon_alipay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_alipay.imageset/purchase_icon_alipay@2x.png -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_alipay.imageset/purchase_icon_alipay@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_alipay.imageset/purchase_icon_alipay@3x.png -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_applepay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "applepay_mark.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_applepay.imageset/applepay_mark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_applepay.imageset/applepay_mark.pdf -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_unionpay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "purchase_icon_unionpay@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "purchase_icon_unionpay@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_unionpay.imageset/purchase_icon_unionpay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_unionpay.imageset/purchase_icon_unionpay@2x.png -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_unionpay.imageset/purchase_icon_unionpay@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_unionpay.imageset/purchase_icon_unionpay@3x.png -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_wechat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "purchase_icon_wechat@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "purchase_icon_wechat@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_wechat.imageset/purchase_icon_wechat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_wechat.imageset/purchase_icon_wechat@2x.png -------------------------------------------------------------------------------- /TwoWayBind/Assets.xcassets/purchase_icon_wechat.imageset/purchase_icon_wechat@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/TwoWayBind/Assets.xcassets/purchase_icon_wechat.imageset/purchase_icon_wechat@3x.png -------------------------------------------------------------------------------- /TwoWayBind/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TwoWayBind/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TwoWayBind/PushSetting/PushSettingData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushSettingData.swift 3 | // RxDealCell 4 | // 5 | // Created by DianQK on 8/8/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | let pushSettingData: [PushSettingSectionModel] = { 14 | 15 | let consumptionItems = [ 16 | PushItemModel(pushType: .confirm, select: Variable(true)), 17 | PushItemModel(pushType: .willExpire, select: Variable(true)), 18 | PushItemModel(pushType: .expired, select: Variable(true)), 19 | PushItemModel(pushType: .refunded, select: Variable(true)), 20 | ] 21 | let consumptionSection = PushSettingSectionModel(model: "消费相关", items: consumptionItems) 22 | 23 | let otherItems = [ 24 | PushItemModel(pushType: .getGift, select: Variable(true)), 25 | PushItemModel(pushType: .couponInfo, select: Variable(true)), 26 | PushItemModel(pushType: .favorite, select: Variable(true)), 27 | ] 28 | let otherSection = PushSettingSectionModel(model: "其他", items: otherItems) 29 | 30 | return [consumptionSection, otherSection] 31 | }() 32 | -------------------------------------------------------------------------------- /TwoWayBind/PushSetting/PushSettingModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushSettingModel.swift 3 | // RxDealCell 4 | // 5 | // Created by DianQK on 8/8/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | import RxDataSources 13 | 14 | enum PushType { 15 | case confirm 16 | case willExpire 17 | case expired 18 | case refunded 19 | case getGift 20 | case couponInfo 21 | case favorite 22 | 23 | var name: String { 24 | switch self { 25 | case .confirm: 26 | return "消费确认" 27 | case .willExpire: 28 | return "订单即将过期" 29 | case .expired: 30 | return "订单已过期" 31 | case .refunded: 32 | return "退款成功" 33 | case .getGift: 34 | return "获得礼券" 35 | case .couponInfo: 36 | return "优惠信息" 37 | case .favorite: 38 | return "喜欢的礼遇有更新" 39 | } 40 | } 41 | } 42 | 43 | struct PushItemModel { 44 | let pushType: PushType 45 | let select: Variable 46 | } 47 | 48 | extension PushItemModel: Hashable, Equatable, IdentifiableType { 49 | var hashValue: Int { 50 | return pushType.hashValue 51 | } 52 | 53 | var identity: Int { 54 | return pushType.hashValue 55 | } 56 | 57 | static func ==(lhs: PushItemModel, rhs: PushItemModel) -> Bool { 58 | return lhs.pushType == rhs.pushType 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TwoWayBind/PushSetting/SelectTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectTableViewCell.swift 3 | // RxDealCell 4 | // 5 | // Created by DianQK on 8/8/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class SelectTableViewCell: UITableViewCell { 14 | 15 | var name: String? { 16 | get { 17 | return nameLabel?.text 18 | } 19 | set { 20 | nameLabel.text = newValue 21 | } 22 | } 23 | 24 | @IBOutlet private weak var nameLabel: UILabel! 25 | @IBOutlet fileprivate weak var selectButton: UIButton! 26 | 27 | } 28 | 29 | extension Reactive where Base: SelectTableViewCell { 30 | var select: ControlProperty { 31 | return base.selectButton.rx.select 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TwoWayBind/PushSetting/SelectTableViewHeaderFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectTableViewHeaderFooterView.swift 3 | // RxDealCell 4 | // 5 | // Created by DianQK on 8/8/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SnapKit 11 | import RxSwift 12 | import RxCocoa 13 | 14 | class SelectTableViewHeaderFooterView: UITableViewHeaderFooterView { 15 | 16 | static let reuseIdentifier = "SelectTableViewHeaderFooterView" 17 | 18 | var name: String? { 19 | get { 20 | return nameLabel.text 21 | } 22 | set { 23 | nameLabel.text = newValue 24 | } 25 | } 26 | 27 | fileprivate lazy var selectSwitch = UISwitch() 28 | private lazy var nameLabel = UILabel() 29 | 30 | override init(reuseIdentifier: String?) { 31 | super.init(reuseIdentifier: reuseIdentifier) 32 | contentView.addSubview(nameLabel) 33 | nameLabel.snp.makeConstraints { (make) in 34 | make.leading.equalTo(self.contentView).offset(30) 35 | make.centerY.equalTo(self.contentView) 36 | } 37 | 38 | contentView.addSubview(selectSwitch) 39 | selectSwitch.snp.makeConstraints { (make) in 40 | make.trailing.equalTo(self.contentView).offset(-30) 41 | make.centerY.equalTo(self.contentView) 42 | } 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | 49 | } 50 | 51 | extension Reactive where Base: UISwitch { 52 | public var isOn: ControlProperty { 53 | let source = self.controlEvent(.valueChanged) 54 | .map { [unowned uiSwitch = self.base] in uiSwitch.isOn } 55 | let sink = UIBindingObserver(UIElement: self.base) { uiSwitch, isOn in 56 | guard uiSwitch.isOn != isOn else { return } 57 | uiSwitch.setOn(isOn, animated: true) 58 | } 59 | return ControlProperty(values: source, valueSink: sink) 60 | } 61 | } 62 | 63 | extension Reactive where Base: SelectTableViewHeaderFooterView { 64 | var isSelected: ControlProperty { 65 | return base.selectSwitch.rx.isOn(animated: true) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /TwoWayBind/SelectPayment/PaymentTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentTableViewCell.swift 3 | // RxDealCell 4 | // 5 | // Created by DianQK on 8/5/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import RxDataSources 13 | 14 | enum Payment: Hashable, Equatable, IdentifiableType { 15 | /// 支付宝 16 | case alipay 17 | /// 微信 18 | case wechat 19 | /// 银联 20 | case unionpay 21 | // Apple Pay 22 | case applepay 23 | 24 | fileprivate var icon: UIImage? { 25 | switch self { 26 | case .alipay: 27 | return #imageLiteral(resourceName: "purchase_icon_alipay") 28 | // return R.image.purchase_icon_alipay() 29 | case .wechat: 30 | return #imageLiteral(resourceName: "purchase_icon_wechat") 31 | // return R.image.purchase_icon_wechat() 32 | case .unionpay: 33 | return #imageLiteral(resourceName: "purchase_icon_unionpay") 34 | // return R.image.purchase_icon_unionpay() 35 | case .applepay: 36 | return #imageLiteral(resourceName: "purchase_icon_applepay") 37 | } 38 | } 39 | 40 | var name: String { 41 | switch self { 42 | case .alipay: 43 | return "支付宝支付" 44 | case .wechat: 45 | return "微信支付" 46 | case .unionpay: 47 | return "银联支付" 48 | case .applepay: 49 | return "Apple Pay" 50 | } 51 | } 52 | 53 | var identity: Int { 54 | return hashValue 55 | } 56 | } 57 | 58 | class PaymentTableViewCell: UITableViewCell { 59 | 60 | @IBOutlet private weak var iconImageView: UIImageView! 61 | @IBOutlet private weak var nameLabel: UILabel! 62 | @IBOutlet fileprivate weak var selectButton: UIButton! 63 | 64 | private var _payment: Payment = Payment.alipay { 65 | didSet { 66 | iconImageView.image = _payment.icon 67 | nameLabel.text = _payment.name 68 | } 69 | } 70 | 71 | func setPayment(_ payment: Payment) { 72 | _payment = payment 73 | } 74 | 75 | } 76 | 77 | extension Reactive where Base: PaymentTableViewCell { 78 | var isSelectedPayment: UIBindingObserver { 79 | return base.selectButton.rx.isSelected 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /UploadImageSimple/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UploadImageSimple 4 | // 5 | // Created by DianQK on 24/03/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /UploadImageSimple/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /UploadImageSimple/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /UploadImageSimple/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /UploadImages/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UploadImageDemo 4 | // 5 | // Created by DianQK on 05/01/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /UploadImages/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /UploadImages/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /UploadImages/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryUsageDescription 6 | NSPhotoLibraryUsageDescription 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /UploadImages/QBImagePickerController+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QBImagePickerController+Rx.swift 3 | // UploadImageDemo 4 | // 5 | // Created by DianQK on 05/01/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QBImagePicker 11 | import RxSwift 12 | import RxCocoa 13 | 14 | extension Reactive where Base: QBImagePickerController { 15 | 16 | public var delegate: DelegateProxy { 17 | return RxQBImagePickerControllerDelegateProxy.proxyForObject(base) 18 | } 19 | 20 | public var didFinishPickingAssets: Observable<[PHAsset]> { 21 | return delegate.methodInvoked(#selector(QBImagePickerControllerDelegate.qb_imagePickerController(_:didFinishPickingAssets:))) 22 | .map { (a) -> [PHAsset] in 23 | return a[1] as! [PHAsset] 24 | } 25 | .do(onNext: { [unowned base = self.base] _ in 26 | base.dismiss(animated: true, completion: nil) 27 | }) 28 | .asObservable() 29 | } 30 | 31 | public var didCancel: Observable<()> { 32 | return delegate.methodInvoked(#selector(QBImagePickerControllerDelegate.qb_imagePickerControllerDidCancel(_:))) 33 | .map { _ in 34 | return () 35 | } 36 | .do(onNext: { [unowned base = self.base] _ in 37 | base.dismiss(animated: true, completion: nil) 38 | }) 39 | .asObservable() 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /UploadImages/RxQBImagePickerControllerDelegateProxy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxQBImagePickerControllerDelegateProxy.swift 3 | // UploadImageDemo 4 | // 5 | // Created by DianQK on 05/01/2017. 6 | // Copyright © 2017 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QBImagePicker 11 | import RxSwift 12 | import RxCocoa 13 | 14 | public class RxQBImagePickerControllerDelegateProxy: DelegateProxy, DelegateProxyType, QBImagePickerControllerDelegate { 15 | 16 | /** 17 | For more information take a look at `DelegateProxyType`. 18 | */ 19 | public class func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) { 20 | let imagePickerController = object as? QBImagePickerController 21 | imagePickerController?.delegate = delegate as? QBImagePickerControllerDelegate 22 | } 23 | 24 | /** 25 | For more information take a look at `DelegateProxyType`. 26 | */ 27 | public class func currentDelegateFor(_ object: AnyObject) -> AnyObject? { 28 | let imagePickerController = object as? QBImagePickerController 29 | return imagePickerController?.delegate 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /UploadImages/UploadImageCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // UploadImageCollectionViewCell.swift 4 | // UploadImageDemo 5 | // 6 | // Created by DianQK on 05/01/2017. 7 | // Copyright © 2017 T. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import RxSwift 12 | 13 | class UploadImageCollectionViewCell: UICollectionViewCell { 14 | 15 | @IBOutlet weak var imageView: UIImageView! 16 | @IBOutlet weak var progressView: UIProgressView! 17 | @IBOutlet weak var retryButton: UIButton! 18 | 19 | private(set) var reuseDisposeBag = DisposeBag() 20 | 21 | override func prepareForReuse() { 22 | super.prepareForReuse() 23 | imageView.image = nil 24 | progressView.progress = 0 25 | reuseDisposeBag = DisposeBag() 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /YepRecord/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RxYepRecord 4 | // 5 | // Created by DianQK on 9/1/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "icon-ipad_29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "icon-iphone_29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "icon-iphone_40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "icon-iphone_40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "icon-iphone_60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "icon-iphone_60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/icon-ipad_29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/AppIcon.appiconset/icon-ipad_29@2x.png -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_29@3x.png -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_40@2x.png -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_40@3x.png -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_60@2x.png -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/AppIcon.appiconset/icon-iphone_60@3x.png -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/button_voice_pause.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button_voice_pause.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/button_voice_pause.imageset/button_voice_pause.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/button_voice_pause.imageset/button_voice_pause.pdf -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/button_voice_play.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button_voice_play.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/button_voice_play.imageset/button_voice_play.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/button_voice_play.imageset/button_voice_play.pdf -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/button_voice_reset.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button_voice_reset.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/button_voice_reset.imageset/button_voice_reset.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/button_voice_reset.imageset/button_voice_reset.pdf -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/voice_indicator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "voice_indicator.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /YepRecord/Assets.xcassets/voice_indicator.imageset/voice_indicator.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/rx-sample-code/d121f3f2e63dff7936356923861486755f4833d7/YepRecord/Assets.xcassets/voice_indicator.imageset/voice_indicator.pdf -------------------------------------------------------------------------------- /YepRecord/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YepRecord/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSMicrophoneUsageDescription 24 | == 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /YepRecord/Utils/AudioBot/AVAudioSession+AudioBot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVAudioSession+AudioBot.swift 3 | // VoiceMemo 4 | // 5 | // Created by NIX on 16/7/27. 6 | // Copyright © 2016年 nixWork. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | extension AVAudioSession { 13 | 14 | var audiobot_canPlay: Bool { 15 | 16 | switch category { 17 | case AVAudioSessionCategoryPlayback: 18 | return true 19 | case AVAudioSessionCategoryPlayAndRecord: 20 | return true 21 | default: 22 | return false 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /YepRecord/Utils/AudioBot/NSFileManager+AudioBot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileManager+AudioBot.swift 3 | // AudioBot 4 | // 5 | // Created by NIX on 15/11/28. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FileManager { 12 | 13 | class func audiobot_cachesURL() -> URL { 14 | 15 | do { 16 | return try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false) 17 | 18 | } catch let error { 19 | fatalError("AudioBot: \(error)") 20 | } 21 | } 22 | 23 | class func audiobot_audioCachesURL() -> URL? { 24 | 25 | let fileManager = FileManager.default 26 | 27 | let audioCachesURL = audiobot_cachesURL().appendingPathComponent("audiobot_audios", isDirectory: true) 28 | 29 | do { 30 | try fileManager.createDirectory(at: audioCachesURL, withIntermediateDirectories: true, attributes: nil) 31 | return audioCachesURL 32 | 33 | } catch let error { 34 | print("AudioBot: \(error)") 35 | } 36 | 37 | return nil 38 | } 39 | 40 | class func audiobot_audioFileURLWithName(name: String) -> URL? { 41 | return audiobot_audioCachesURL().map { $0.appendingPathComponent("\(name).m4a") } 42 | } 43 | 44 | class func audiobot_removeAudioAtFileURL(fileURL: URL) { 45 | 46 | do { 47 | try FileManager.default.removeItem(at: fileURL) 48 | 49 | } catch let error { 50 | print("AudioBot: \(error)") 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /YepRecord/Utils/SafeCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafeCollection.swift 3 | // Expandable 4 | // 5 | // Created by DianQK on 8/18/16. 6 | // Copyright © 2016 T. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct SafeCollection { //: Collection { 12 | /// Returns a subsequence containing all but the given number of initial 13 | /// elements. 14 | /// 15 | /// If the number of elements to drop exceeds the number of elements in 16 | /// the sequence, the result is an empty subsequence. 17 | /// 18 | /// let numbers = [1, 2, 3, 4, 5] 19 | /// print(numbers.dropFirst(2)) 20 | /// // Prints "[3, 4, 5]" 21 | /// print(numbers.dropFirst(10)) 22 | /// // Prints "[]" 23 | /// 24 | /// - Parameter n: The number of elements to drop from the beginning of 25 | /// the sequence. `n` must be greater than or equal to zero. 26 | /// - Returns: A subsequence starting after the specified number of 27 | /// elements. 28 | /// 29 | /// - Complexity: O(*n*), where *n* is the number of elements to drop from 30 | /// the beginning of the sequence. 31 | public typealias SubSequence = Base.SubSequence 32 | public func dropFirst(_ n: Int) -> SubSequence { 33 | return _base.dropFirst(n) 34 | } 35 | 36 | fileprivate var _base: Base 37 | public init(_ base: Base) { 38 | _base = base 39 | } 40 | 41 | public typealias Index = Base.Index 42 | public var startIndex: Index { 43 | return _base.startIndex 44 | } 45 | 46 | public var endIndex: Index { 47 | return _base.endIndex 48 | } 49 | 50 | public subscript(index: Base.Index) -> Base.Iterator.Element? { 51 | if startIndex <= index && index < endIndex { 52 | return _base[index] 53 | } 54 | return nil 55 | } 56 | 57 | public subscript(bounds: Range) -> Base.SubSequence? { 58 | if startIndex <= bounds.lowerBound && bounds.upperBound < endIndex { 59 | return _base[bounds] 60 | } 61 | return nil 62 | } 63 | 64 | var safe: SafeCollection { //Allows to chain ".safe" without side effects 65 | return self 66 | } 67 | } 68 | 69 | public extension Collection { 70 | var safe: SafeCollection { 71 | return SafeCollection(self) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /YepRecord/Utils/UIFont+Yep.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Yep.swift 3 | // Yep 4 | // 5 | // Created by NIX on 15/3/30. 6 | // Copyright (c) 2015年 Catch Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIFont { 12 | 13 | class func chatTextFont() -> UIFont { 14 | return UIFont.systemFont(ofSize: 16) 15 | } 16 | 17 | class func feedMessageFont() -> UIFont { 18 | return UIFont.systemFont(ofSize: 17) 19 | } 20 | 21 | class func feedSkillFont() -> UIFont { 22 | return UIFont.systemFont(ofSize: 12) 23 | } 24 | 25 | class func feedBottomLabelsFont() -> UIFont { 26 | return UIFont.systemFont(ofSize: 14) 27 | } 28 | 29 | class func feedVoiceTimeLengthFont() -> UIFont { 30 | return UIFont.systemFont(ofSize: 12) 31 | } 32 | } 33 | 34 | extension UIFont { 35 | 36 | class func skillDiscoverTextFont() -> UIFont { 37 | return UIFont.systemFont(ofSize: 11) 38 | } 39 | 40 | class func skillTextFont() -> UIFont { 41 | return UIFont.systemFont(ofSize: 14) 42 | } 43 | 44 | class func skillTextLargeFont() -> UIFont { 45 | return UIFont.systemFont(ofSize: 20) 46 | } 47 | 48 | class func skillHomeTextLargeFont() -> UIFont { 49 | return UIFont.systemFont(ofSize: 18) 50 | } 51 | 52 | class func skillHomeButtonFont() -> UIFont { 53 | return UIFont.systemFont(ofSize: 16) 54 | } 55 | 56 | class func barButtonFont() -> UIFont { 57 | return UIFont.systemFont(ofSize: 14) 58 | } 59 | 60 | class func navigationBarTitleFont() -> UIFont { // make sure it's the same as system use 61 | return UIFont.boldSystemFont(ofSize: 17) 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /YepRecord/Views/AudioWaves/YepWaverView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YepWaverView.swift 3 | // Yep 4 | // 5 | // Created by kevinzhow on 15/4/1. 6 | // Copyright (c) 2015年 Catch Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class YepWaverView: UIView { 12 | 13 | var waver: Waver! 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | setup() 18 | } 19 | 20 | required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | setup() 23 | } 24 | 25 | fileprivate func setup() { 26 | waver = Waver(frame: CGRect(x: 0, y: self.bounds.height/2.0 - 50.0 - 40.0, width: self.bounds.width, height: 100.0)) 27 | self.backgroundColor = UIColor(white: 1.0, alpha: 0.9) 28 | } 29 | 30 | override func willMove(toSuperview newSuperview: UIView?) { 31 | super.willMove(toSuperview: newSuperview) 32 | self.addSubview(waver) 33 | } 34 | 35 | override func removeFromSuperview() { 36 | super.removeFromSuperview() 37 | waver.removeFromSuperview() 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /YepRecord/Views/HorizontalLineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalLineView.swift 3 | // Yep 4 | // 5 | // Created by nixzhu on 15/9/30. 6 | // Copyright © 2015年 Catch Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | final class HorizontalLineView: UIView { 13 | 14 | @IBInspectable 15 | var lineColor: UIColor = UIColor.yepBorderColor() { 16 | didSet { 17 | setNeedsDisplay() 18 | } 19 | } 20 | @IBInspectable 21 | var lineWidth: CGFloat = 1.0 / UIScreen.main.scale { 22 | didSet { 23 | setNeedsDisplay() 24 | } 25 | } 26 | 27 | @IBInspectable 28 | var leftMargin: CGFloat = 0 { 29 | didSet { 30 | setNeedsDisplay() 31 | } 32 | } 33 | 34 | @IBInspectable 35 | var rightMargin: CGFloat = 0 { 36 | didSet { 37 | setNeedsDisplay() 38 | } 39 | } 40 | 41 | @IBInspectable 42 | var atBottom: Bool = true { 43 | didSet { 44 | setNeedsDisplay() 45 | } 46 | } 47 | 48 | // MARK: Draw 49 | 50 | override func draw(_ rect: CGRect) { 51 | super.draw(rect) 52 | 53 | lineColor.setStroke() 54 | 55 | let context = UIGraphicsGetCurrentContext()! 56 | 57 | context.setLineWidth(lineWidth) 58 | 59 | let y: CGFloat 60 | let fullHeight = rect.height 61 | 62 | if atBottom { 63 | y = fullHeight - lineWidth * 0.5 64 | } else { 65 | y = lineWidth * 0.5 66 | } 67 | context.move(to: CGPoint(x: leftMargin, y: y)) 68 | context.addLine(to: CGPoint(x: rect.width - rightMargin, y: y)) 69 | 70 | context.strokePath() 71 | 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # Customise this file, documentation can be found here: 2 | # https://github.com/fastlane/fastlane/tree/master/fastlane/docs 3 | # All available actions: https://docs.fastlane.tools/actions 4 | # can also be listed using the `fastlane actions` command 5 | 6 | # Change the syntax highlighting to Ruby 7 | # All lines starting with a # are ignored when running `fastlane` 8 | 9 | # If you want to automatically update fastlane if a new version is available: 10 | # update_fastlane 11 | 12 | # This is the minimum version number required. 13 | # Update this, if you use features of a newer version 14 | fastlane_version "2.13.0" 15 | 16 | default_platform :ios 17 | 18 | platform :ios do 19 | before_all do 20 | # carthage 21 | end 22 | 23 | lane :build do 24 | [ 25 | "TwoWayBind", 26 | "Stopwatch", 27 | "HandleError", 28 | "Questions", 29 | "PDFExpertContents", 30 | "RxDataSourcesExample", 31 | "CustomRefresh", 32 | "TextInput", 33 | "UploadImages", 34 | "YepRecord", 35 | "Concurrency", 36 | "GitHubSearchRepositoriesX", 37 | "Form", 38 | "UploadImageSimple", 39 | "LocalAuthenticationDemo" 40 | ].each do |scheme| 41 | xcodebuild(scheme: scheme, project: "rx-sample-code.xcodeproj") 42 | end 43 | # gym(project: "rx-sample-code.xcodeproj") 44 | # xcodebuild(scheme: "HandleError", project: "rx-sample-code.xcodeproj") 45 | end 46 | 47 | end 48 | 49 | 50 | # More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md 51 | # All available actions: https://docs.fastlane.tools/actions 52 | 53 | # fastlane reports which actions are used 54 | # No personal data is recorded. Learn more at https://github.com/fastlane/enhancer 55 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | ## Choose your installation method: 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Homebrew 16 | Installer Script 17 | Rubygems 18 |
macOSmacOSmacOS or Linux with Ruby 2.0.0 or above
brew cask install fastlaneDownload the zip file. Then double click on the install script (or run it in a terminal window).sudo gem install fastlane -NV
30 | # Available Actions 31 | ## iOS 32 | ### ios build 33 | ``` 34 | fastlane ios build 35 | ``` 36 | 37 | 38 | ---- 39 | 40 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 41 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 42 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 43 | -------------------------------------------------------------------------------- /rx-sample-code.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | --------------------------------------------------------------------------------