├── .gitignore ├── .gitmodules ├── Cartfile.private ├── Cartfile.resolved ├── LICENSE ├── README.md ├── ReactKitCatalog-OSX ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── ViewController.swift ├── ReactKitCatalog-iOS ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Catalog.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── logo-60@2x.png │ │ ├── logo-60@3x.png │ │ ├── logo-76.png │ │ └── logo-76@2x.png ├── Info.plist ├── MasterViewController.swift └── Samples │ ├── ArrayKVOViewController.swift │ ├── ArrayKVOViewController.xib │ ├── ArrayKVOViewModel.swift │ ├── ButtonViewController.swift │ ├── ButtonViewController.xib │ ├── GestureViewController.swift │ ├── GestureViewController.xib │ ├── IncrementalSearchViewController.swift │ ├── MultipleTextFieldViewController.swift │ ├── MultipleTextFieldViewController.xib │ ├── TextFieldViewController.swift │ ├── TextFieldViewController.xib │ ├── TimerViewController.swift │ ├── TimerViewController.xib │ ├── WhoToFollowViewController.swift │ └── WhoToFollowViewController.xib ├── ReactKitCatalog.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── Shared └── Helper.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | *.xccheckout 13 | *.moved-aside 14 | DerivedData 15 | *.hmap 16 | *.ipa 17 | *.xcuserstate 18 | 19 | Carthage/Build 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/SwiftTask"] 2 | path = Carthage/Checkouts/SwiftTask 3 | url = https://github.com/ReactKit/SwiftTask.git 4 | [submodule "Carthage/Checkouts/ReactKit"] 5 | path = Carthage/Checkouts/ReactKit 6 | url = https://github.com/ReactKit/ReactKit.git 7 | [submodule "Carthage/Checkouts/Alamofire"] 8 | path = Carthage/Checkouts/Alamofire 9 | url = https://github.com/Alamofire/Alamofire.git 10 | [submodule "Carthage/Checkouts/SwiftyJSON"] 11 | path = Carthage/Checkouts/SwiftyJSON 12 | url = https://github.com/SwiftyJSON/SwiftyJSON.git 13 | [submodule "Carthage/Checkouts/Async"] 14 | path = Carthage/Checkouts/Async 15 | url = https://github.com/duemunk/Async.git 16 | [submodule "Carthage/Checkouts/Dollar.swift"] 17 | path = Carthage/Checkouts/Dollar.swift 18 | url = https://github.com/ankurp/Dollar.swift.git 19 | [submodule "Carthage/Checkouts/HanekeSwift"] 20 | path = Carthage/Checkouts/HanekeSwift 21 | url = https://github.com/meteochu/HanekeSwift.git 22 | [submodule "Carthage/Checkouts/BigBrother"] 23 | path = Carthage/Checkouts/BigBrother 24 | url = https://github.com/marcelofabri/BigBrother.git 25 | [submodule "Carthage/Checkouts/ReactKitCalculator"] 26 | url = https://github.com/ReactKit/ReactKitCalculator.git 27 | path = Carthage/Checkouts/ReactKitCalculator 28 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "ReactKit/ReactKitCalculator" "swift/2.0" 2 | 3 | github "Alamofire/Alamofire" "3.0.0-beta.1" #~> 1.2.3 4 | github "SwiftyJSON/SwiftyJSON" "xcode7" #~> 2.2.0 5 | github "duemunk/Async" "feature/Swift_2.0" 6 | github "ankurp/Dollar.swift" ~> 4.0.1 7 | #github "Haneke/HanekeSwift" ~> 0.9.1 8 | github "meteochu/HanekeSwift" "swift-2.0" 9 | github "marcelofabri/BigBrother" ~> 0.3.0 -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "3.0.0-beta.1" 2 | github "duemunk/Async" "abe23c18d8d3a708a325b5db10231388e1ca5b88" 3 | github "marcelofabri/BigBrother" "0.3.0" 4 | github "ankurp/Dollar.swift" "4.0.1" 5 | github "meteochu/HanekeSwift" "f87220fccba7810955fe5392ebd492a5c9baf050" 6 | github "ReactKit/SwiftTask" "4.0.0" 7 | github "SwiftyJSON/SwiftyJSON" "6eb4cda062fa09c4658e4184f92d822058f226e3" 8 | github "ReactKit/ReactKit" "0.12.0" 9 | github "ReactKit/ReactKitCalculator" "54d5d49696292f385f22fa9fd722da5af3548974" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 ReactKit 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ReactKitCatalog 2 | =============== 3 | 4 | ReactKit UI examples. 5 | -------------------------------------------------------------------------------- /ReactKitCatalog-OSX/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ReactKitCatalog-OSX 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | func applicationDidFinishLaunching(aNotification: NSNotification) { 15 | println("2014/10/06 Mac Demo is not ready yet.") 16 | } 17 | 18 | func applicationWillTerminate(aNotification: NSNotification) { 19 | // Insert code here to tear down your application 20 | } 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ReactKitCatalog-OSX/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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | Default 510 | 511 | 512 | 513 | 514 | 515 | 516 | Left to Right 517 | 518 | 519 | 520 | 521 | 522 | 523 | Right to Left 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Default 535 | 536 | 537 | 538 | 539 | 540 | 541 | Left to Right 542 | 543 | 544 | 545 | 546 | 547 | 548 | Right to Left 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | -------------------------------------------------------------------------------- /ReactKitCatalog-OSX/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /ReactKitCatalog-OSX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 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 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2014年 Yasuhiro Inami. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /ReactKitCatalog-OSX/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ReactKitCatalog-OSX 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ViewController: NSViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | override var representedObject: AnyObject? { 20 | didSet { 21 | // Update the view, if already loaded. 22 | } 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ReactKitCatalog-iOS 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | //import BigBrother 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | func _setupAppearance() 19 | { 20 | let font = UIFont(name: "AvenirNext-Medium", size: 16)! 21 | 22 | UINavigationBar.appearance().titleTextAttributes = [ NSFontAttributeName : font ] 23 | UIBarButtonItem.appearance().setTitleTextAttributes([ NSFontAttributeName : font ], forState: .Normal) 24 | // UIButton.appearance().titleLabel?.font = font 25 | // UILabel.appearance().font = font 26 | // UITextField.appearance().font = font 27 | // UITextView.appearance().font = font 28 | } 29 | 30 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool 31 | { 32 | // 2015/07/18 comment-out: BigBrother not working in Swift 2 (Xcode7-beta3) 33 | // setup BigBrother (networkActivityIndicator) 34 | // BigBrother.addToSharedSession() 35 | // BigBrother.addToSessionConfiguration(Alamofire.Manager.sharedInstance.session.configuration) 36 | 37 | self._setupAppearance() 38 | 39 | let splitVC = self.window!.rootViewController as! UISplitViewController 40 | splitVC.delegate = self 41 | 42 | let mainNavC = splitVC.viewControllers[0] as! UINavigationController 43 | // let detailNavC = splitVC.viewControllers[1] as UINavigationController 44 | 45 | let mainVC = mainNavC.topViewController as! MasterViewController 46 | 47 | // NOTE: use dispatch_after to check `splitVC.collapsed` after delegation is complete (for iPad) 48 | // FIXME: look for better solution 49 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1_000_000), dispatch_get_main_queue()) { 50 | if !splitVC.collapsed { 51 | mainVC.showDetailViewControllerAtIndex(0) 52 | } 53 | } 54 | 55 | return true 56 | } 57 | 58 | func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool 59 | { 60 | return true 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Catalog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Catalog.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Catalog 12 | { 13 | let title: String? 14 | let description: String? 15 | let class_: UIViewController.Type 16 | let hasXib: Bool 17 | let selected: Bool 18 | 19 | static func allCatalogs() -> [Catalog] 20 | { 21 | return [ 22 | Catalog(title: "NSTimer", description: "pause()/resume()", class_: TimerViewController.self), 23 | Catalog(title: "UIButton/BarButton", description: "Basic", class_: ButtonViewController.self), 24 | Catalog(title: "UITextField", description: "throttle()/debounce()", class_: TextFieldViewController.self), 25 | Catalog(title: "UITextField (Multiple)", description: "Login example", class_: MultipleTextFieldViewController.self), 26 | Catalog(title: "UIGestureRecognizer", description: "merge2All()", class_: GestureViewController.self), 27 | Catalog(title: "Who To Follow", description: "Suggestion box", class_: WhoToFollowViewController.self), 28 | Catalog(title: "Calculator", description: "Mimics iOS Calculator.app", class_: CalculatorViewController.self), 29 | Catalog(title: "Array + Table", description: "DynamicArray + KVO.detailedStream()", class_: ArrayKVOViewController.self), 30 | Catalog(title: "Incremental Search", description: "throttle + distinct + switchLatestInner", class_: IncrementalSearchViewController.self, hasXib: false, selected: true) 31 | ] 32 | } 33 | 34 | init(title: String?, description: String?, class_: UIViewController.Type, hasXib: Bool = true, selected: Bool = false) 35 | { 36 | self.title = title 37 | self.description = description 38 | self.class_ = class_ 39 | self.hasXib = hasXib 40 | self.selected = selected 41 | } 42 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Images.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 | "size" : "60x60", 25 | "idiom" : "iphone", 26 | "filename" : "logo-60@2x.png", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "size" : "60x60", 31 | "idiom" : "iphone", 32 | "filename" : "logo-60@3x.png", 33 | "scale" : "3x" 34 | }, 35 | { 36 | "idiom" : "ipad", 37 | "size" : "29x29", 38 | "scale" : "1x" 39 | }, 40 | { 41 | "idiom" : "ipad", 42 | "size" : "29x29", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "40x40", 48 | "scale" : "1x" 49 | }, 50 | { 51 | "idiom" : "ipad", 52 | "size" : "40x40", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "size" : "76x76", 57 | "idiom" : "ipad", 58 | "filename" : "logo-76.png", 59 | "scale" : "1x" 60 | }, 61 | { 62 | "size" : "76x76", 63 | "idiom" : "ipad", 64 | "filename" : "logo-76@2x.png", 65 | "scale" : "2x" 66 | } 67 | ], 68 | "info" : { 69 | "version" : 1, 70 | "author" : "xcode" 71 | } 72 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactKit/ReactKitCatalog/f8bd8e8755f2004c9da5ce30787976977c5abc8e/ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-60@2x.png -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactKit/ReactKitCatalog/f8bd8e8755f2004c9da5ce30787976977c5abc8e/ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-60@3x.png -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactKit/ReactKitCatalog/f8bd8e8755f2004c9da5ce30787976977c5abc8e/ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-76.png -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactKit/ReactKitCatalog/f8bd8e8755f2004c9da5ce30787976977c5abc8e/ReactKitCatalog-iOS/Images.xcassets/AppIcon.appiconset/logo-76@2x.png -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // ReactKitCatalog-iOS 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Dollar 11 | 12 | class MasterViewController: UITableViewController 13 | { 14 | let catalogs = Catalog.allCatalogs() 15 | 16 | override func awakeFromNib() 17 | { 18 | super.awakeFromNib() 19 | 20 | if UIDevice.currentDevice().userInterfaceIdiom == .Pad { 21 | self.clearsSelectionOnViewWillAppear = false 22 | self.preferredContentSize = CGSize(width: 320.0, height: 600.0) 23 | } 24 | } 25 | 26 | override func viewDidLoad() 27 | { 28 | super.viewDidLoad() 29 | 30 | // auto-select 31 | if let index = ($.findIndex(self.catalogs) { $0.selected }) { 32 | self.showDetailViewControllerAtIndex(index) 33 | } 34 | } 35 | 36 | //-------------------------------------------------- 37 | // MARK: - UITableViewDataSource 38 | //-------------------------------------------------- 39 | 40 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int 41 | { 42 | return 1 43 | } 44 | 45 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 46 | { 47 | return self.catalogs.count 48 | } 49 | 50 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 51 | { 52 | let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 53 | 54 | let catalog = self.catalogs[indexPath.row] 55 | cell.textLabel?.text = catalog.title 56 | cell.detailTextLabel?.text = catalog.description 57 | 58 | return cell 59 | } 60 | 61 | // MARK: UITableViewDelegate 62 | 63 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) 64 | { 65 | self.showDetailViewControllerAtIndex(indexPath.row) 66 | } 67 | 68 | func showDetailViewControllerAtIndex(index: Int) 69 | { 70 | let catalog = self.catalogs[index] 71 | 72 | // 73 | // change Swift's className to Obj-C nibName 74 | // 75 | // e.g. 76 | // NSStringFromClass(catalog.class_) 77 | // = "ReactKitCatalog_iOS.TextFieldViewController" 78 | // 79 | let className = NSStringFromClass(catalog.class_).componentsSeparatedByString(".").last 80 | 81 | if let className = className { 82 | let newVC: UIViewController 83 | if catalog.hasXib { 84 | newVC = catalog.class_.init(nibName: className, bundle: nil) 85 | } 86 | else { 87 | newVC = catalog.class_.init() 88 | } 89 | 90 | // let newVC = catalog.class_(nibName: className, bundle: nil) 91 | let newNavC = UINavigationController(rootViewController: newVC) 92 | self.splitViewController?.showDetailViewController(newNavC, sender: self) 93 | 94 | newVC.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() 95 | newVC.navigationItem.leftItemsSupplementBackButton = true 96 | 97 | } 98 | } 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/ArrayKVOViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayKVOViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2015/03/21. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | import Dollar 12 | import Async 13 | 14 | let CELL_IDENTIFIER = "Cell" 15 | 16 | class ArrayKVOViewController: UITableViewController 17 | { 18 | typealias SectionData = ArrayKVOViewModel.SectionData 19 | typealias RowData = ArrayKVOViewModel.RowData 20 | 21 | let viewModel: ArrayKVOViewModel 22 | 23 | let insertButtonItem = UIBarButtonItem(title: "Insert", style: .Plain, target: nil, action: nil) 24 | let replaceButtonItem = UIBarButtonItem(title: "Replace", style: .Plain, target: nil, action: nil) 25 | let removeButtonItem = UIBarButtonItem(title: "Remove", style: .Plain, target: nil, action: nil) 26 | let decrementButtonItem = UIBarButtonItem(title: "[-]", style: .Plain, target: nil, action: nil) 27 | let toggleButtonItem = UIBarButtonItem(title: nil, style: .Plain, target: nil, action: nil) 28 | let incrementButtonItem = UIBarButtonItem(title: "[+]", style: .Plain, target: nil, action: nil) 29 | let flexibleButtonItem = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil) 30 | 31 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) 32 | { 33 | self.viewModel = ArrayKVOViewModel() 34 | 35 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) 39 | { 40 | self.viewModel = ArrayKVOViewModel() 41 | 42 | super.init(coder: aDecoder) 43 | } 44 | 45 | deinit 46 | { 47 | // print("[deinit] \(self)") 48 | } 49 | 50 | override func viewDidLoad() 51 | { 52 | super.viewDidLoad() 53 | 54 | self._setupViews() 55 | self._setupStreams() 56 | self._performDemo() 57 | } 58 | 59 | func _setupViews() 60 | { 61 | self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: CELL_IDENTIFIER) 62 | self.tableView.editing = true 63 | 64 | self.toolbarItems = [ 65 | self.flexibleButtonItem, 66 | self.insertButtonItem, self.replaceButtonItem, self.removeButtonItem, 67 | self.flexibleButtonItem, 68 | self.decrementButtonItem, self.toggleButtonItem, self.incrementButtonItem, 69 | self.flexibleButtonItem 70 | ] 71 | self.navigationController?.setToolbarHidden(false, animated: false) 72 | } 73 | 74 | func _setupStreams() 75 | { 76 | // REACT: insert button 77 | let insertButtonStream = self.insertButtonItem.stream().ownedBy(self) 78 | insertButtonStream ~> { [unowned self] _ in 79 | print("") 80 | print("[insert button]") 81 | 82 | self.viewModel.insertRandomSectionsOrRows() 83 | } 84 | 85 | // REACT: replace button 86 | let replaceButtonStream = self.replaceButtonItem.stream().ownedBy(self) 87 | replaceButtonStream ~> { [unowned self] _ in 88 | print("") 89 | print("[replace button]") 90 | 91 | self.viewModel.replaceRandomSectionsOrRows() 92 | } 93 | 94 | // REACT: remove button 95 | let removeButtonStream = self.removeButtonItem.stream().ownedBy(self) 96 | removeButtonStream ~> { [unowned self] _ in 97 | print("") 98 | print("[remove button]") 99 | 100 | self.viewModel.removeRandomSectionsOrRows() 101 | } 102 | 103 | // REACT: decrement button 104 | let decrementButtonStream = self.decrementButtonItem.stream().ownedBy(self) 105 | decrementButtonStream ~> { [unowned self] _ in 106 | print("") 107 | print("[decrement button]") 108 | 109 | self.viewModel.changeMaxCount = max(self.viewModel.changeMaxCount-1, 1) 110 | } 111 | 112 | // REACT: increment button 113 | let incrementButtonStream = self.incrementButtonItem.stream().ownedBy(self) 114 | incrementButtonStream ~> { [unowned self] _ in 115 | print("") 116 | print("[increment button]") 117 | 118 | self.viewModel.changeMaxCount = min(self.viewModel.changeMaxCount+1, Int.max) 119 | } 120 | 121 | // REACT: section/row toggle button 122 | let toggleButtonStream = self.toggleButtonItem.stream().ownedBy(self) 123 | toggleButtonStream ~> { [unowned self] _ in 124 | print("") 125 | print("[toggle button]") 126 | 127 | self.viewModel.tableLocation.toggle() 128 | } 129 | 130 | // REACT: changeMaxCount label 131 | let changeMaxCountStream: Stream = [ 132 | KVO.startingStream(self.viewModel, "changeMaxCount"), 133 | KVO.startingStream(self.viewModel, "tableLocationString") 134 | ] 135 | |> combineLatestAll 136 | |> map { values -> AnyObject? in "\(values[0]!) \(values[1]!)" } // e.g. "1 Section" 137 | 138 | changeMaxCountStream.ownedBy(self) 139 | (self.toggleButtonItem, "title") <~ changeMaxCountStream 140 | 141 | // REACT: sections changed 142 | let sectionDatasChangedStream = self.viewModel.sectionDatas.stream().ownedBy(self.viewModel) 143 | sectionDatasChangedStream ~> { [unowned self] sectionDatas, sectionChange, sectionIndexSet in 144 | 145 | print("") 146 | print("[sectionDatas changed]") 147 | print("sectionChange = \(sectionChange)") 148 | print("sectionDatas = \(sectionDatas ?? [])") 149 | print("sectionIndexSet = \(sectionIndexSet)") 150 | 151 | if sectionChange == .Insertion || sectionChange == .Replacement { 152 | 153 | sectionIndexSet.enumerateIndexesUsingBlock { sectionIndex, stop in 154 | 155 | let sectionData = self.viewModel.sectionDatas.proxy[sectionIndex] as! SectionData 156 | 157 | // REACT: rows changed 158 | let rowDatasChangedStream = sectionData.rowDatas.stream().ownedBy(sectionData) 159 | rowDatasChangedStream ~> { [weak sectionData] rowDatas, rowChange, rowIndexSet in 160 | 161 | let sectionData: SectionData! = sectionData // strongify 162 | if sectionData == nil { return } 163 | 164 | print("") 165 | print("[rowDatas changed]") 166 | print("rowChange = \(rowChange)") 167 | print("rowDatas = \(rowDatas ?? [])") 168 | print("rowIndexSet = \(rowIndexSet)") 169 | 170 | // NOTE: sectionIndex needs to be re-evaluated on rows changed 171 | let sectionIndex = self.viewModel.sectionDatas.proxy.indexOfObject(sectionData) 172 | 173 | var indexPaths: [NSIndexPath] = [] 174 | rowIndexSet.enumerateIndexesUsingBlock { rowIndex, stop in 175 | indexPaths.append(NSIndexPath(forRow: rowIndex, inSection: sectionIndex)) 176 | } 177 | 178 | self.tableView.beginUpdates() 179 | 180 | switch rowChange { 181 | case .Insertion: 182 | self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Right) 183 | case .Replacement: 184 | self.tableView.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: .Right) 185 | case .Removal: 186 | self.tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Left) 187 | default: 188 | break 189 | } 190 | 191 | self.tableView.endUpdates() 192 | } 193 | } 194 | } 195 | 196 | self.tableView.beginUpdates() 197 | 198 | switch sectionChange { 199 | case .Insertion: 200 | self.tableView.insertSections(sectionIndexSet, withRowAnimation: .Right) 201 | case .Replacement: 202 | self.tableView.reloadSections(sectionIndexSet, withRowAnimation: .Right) 203 | case .Removal: 204 | self.tableView.deleteSections(sectionIndexSet, withRowAnimation: .Left) 205 | default: 206 | break 207 | } 208 | 209 | self.tableView.endUpdates() 210 | } 211 | 212 | } 213 | 214 | func _performDemo() 215 | { 216 | self.tableView.userInteractionEnabled = false 217 | 218 | let step = 0.5 // sec 219 | 220 | Async.main(after: 0.2 + step * 0) { 221 | print("*** addObject (section & row) ***") 222 | 223 | self.viewModel.sectionDatas.proxy.addObject(SectionData(title: "Section 1", rowDataArray: [ 224 | RowData(title: "title 1-0"), 225 | RowData(title: "title 1-1") 226 | ])) 227 | } 228 | 229 | Async.main(after: 0.2 + step * 1) { 230 | print("*** insertObject ***") 231 | 232 | self.viewModel.sectionDatas.proxy.insertObject(SectionData(title: "Section 0", rowDataArray: [ 233 | RowData(title: "title 0-0") 234 | ]), atIndex: 0) 235 | } 236 | 237 | Async.main(after: 0.2 + step * 2) { 238 | print("*** replaceObjectAtIndex ***") 239 | 240 | self.viewModel.sectionDatas.proxy.replaceObjectAtIndex(0, withObject: SectionData(title: "Section 0b", rowDataArray: [ 241 | RowData(title: "title 0-1b") 242 | ])) 243 | } 244 | 245 | Async.main(after: 0.2 + step * 3) { 246 | print("*** removeObjectAtIndex ***") 247 | 248 | self.viewModel.sectionDatas.proxy.removeObjectAtIndex(0) 249 | } 250 | 251 | Async.main(after: 0.2 + step * 4) { 252 | print("*** addObject (row) ***") 253 | 254 | let sectionData = self.viewModel.sectionDatas.proxy[0] as! SectionData 255 | sectionData.rowDatas.proxy.addObject(RowData(title: "title 1-2")) 256 | 257 | self.tableView.userInteractionEnabled = true 258 | } 259 | } 260 | 261 | // MARK: - Table view data source 262 | 263 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int 264 | { 265 | return self.viewModel.sectionDatas.proxy.count 266 | } 267 | 268 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 269 | { 270 | let sectionData = self.viewModel.sectionDatas.proxy[section] as! SectionData 271 | return sectionData.rowDatas.proxy.count 272 | } 273 | 274 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 275 | { 276 | let cell = tableView.dequeueReusableCellWithIdentifier(CELL_IDENTIFIER, forIndexPath: indexPath) 277 | 278 | cell.textLabel?.text = self.viewModel.sectionDatas.proxy[indexPath.section][indexPath.row].title 279 | 280 | return cell 281 | } 282 | 283 | override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool 284 | { 285 | // Return NO if you do not want the specified item to be editable. 286 | return true 287 | } 288 | 289 | override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) 290 | { 291 | if editingStyle == .Delete { 292 | 293 | let sectionData = self.viewModel.sectionDatas.proxy[indexPath.section] as! SectionData 294 | sectionData.rowDatas.proxy.removeObjectAtIndex(indexPath.row) 295 | 296 | } else if editingStyle == .Insert { 297 | 298 | } 299 | } 300 | 301 | /* 302 | // Override to support rearranging the table view. 303 | override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { 304 | 305 | } 306 | */ 307 | 308 | /* 309 | // Override to support conditional rearranging of the table view. 310 | override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool { 311 | // Return NO if you do not want the item to be re-orderable. 312 | return true 313 | } 314 | */ 315 | 316 | override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? 317 | { 318 | return self.viewModel.sectionDatas.proxy[section].title 319 | } 320 | 321 | // MARK: UITableViewDelegate 322 | 323 | override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 324 | { 325 | return 30 326 | } 327 | 328 | } 329 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/ArrayKVOViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/ArrayKVOViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayKVOViewModel.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2015/03/21. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactKit 11 | import Dollar 12 | 13 | let defaultTableLocation = ArrayKVOViewModel.TableLocation.Section 14 | 15 | class ArrayKVOViewModel: NSObject 16 | { 17 | var sectionDatas = DynamicArray/**/() 18 | 19 | /// used as insert/replace/remove maxCount 20 | dynamic var changeMaxCount = 1 21 | 22 | // workaround for KVO-streaming Swift enum 23 | private(set) dynamic var tableLocationString = defaultTableLocation.rawValue 24 | 25 | // NOTE: `dynamic var` is not available 26 | var tableLocation: TableLocation = defaultTableLocation 27 | { 28 | didSet(oldValue) { 29 | self.tableLocationString = self.tableLocation.rawValue 30 | } 31 | } 32 | } 33 | 34 | /// helper methods 35 | extension ArrayKVOViewModel 36 | { 37 | func insertRandomSectionsOrRows() 38 | { 39 | switch self.tableLocation { 40 | case .Section: 41 | self._insertRandomSections() 42 | 43 | case .Row: 44 | self._insertRandomRows() 45 | } 46 | } 47 | 48 | private func _insertRandomSections() 49 | { 50 | precondition(self.changeMaxCount > 0) 51 | 52 | let sectionCount = self.sectionDatas.proxy.count 53 | 54 | let indexes = _pickRandom(0...sectionCount, self.changeMaxCount) 55 | let indexSet = NSMutableIndexSet(indexes: indexes) 56 | 57 | let sectionDatas = [Int](count: indexSet.count, repeatedValue: 0) 58 | .map { _ in SectionData.randomData() } 59 | 60 | // insert sections 61 | self.sectionDatas.proxy.insertObjects(sectionDatas, atIndexes: indexSet) 62 | } 63 | 64 | private func _insertRandomRows() 65 | { 66 | precondition(self.changeMaxCount > 0) 67 | 68 | let sectionCount = self.sectionDatas.proxy.count 69 | 70 | // insert section if needed 71 | if sectionCount == 0 { 72 | self.sectionDatas.proxy.addObject(SectionData.emptyData()) 73 | } 74 | 75 | let section = $.random(sectionCount) 76 | let sectionData = self.sectionDatas.proxy[section] as! SectionData 77 | let rowCount = sectionData.rowDatas.proxy.count 78 | 79 | let indexes = _pickRandom(0...rowCount, self.changeMaxCount) 80 | let indexSet = NSMutableIndexSet(indexes: indexes) 81 | 82 | let rowDatas = [Int](count: indexSet.count, repeatedValue: 0).map { _ in RowData.randomData() } 83 | 84 | // insert rows 85 | sectionData.rowDatas.proxy.insertObjects(rowDatas, atIndexes: indexSet) 86 | } 87 | 88 | func replaceRandomSectionsOrRows() 89 | { 90 | switch self.tableLocation { 91 | case .Section: 92 | self._replaceRandomSections() 93 | 94 | case .Row: 95 | self._replaceRandomRows() 96 | } 97 | } 98 | 99 | private func _replaceRandomSections() 100 | { 101 | precondition(self.changeMaxCount > 0) 102 | 103 | let sectionCount = self.sectionDatas.proxy.count 104 | 105 | let indexes = _pickRandom(0.. 0) 117 | 118 | let sectionCount = self.sectionDatas.proxy.count 119 | 120 | let section = $.random(sectionCount) 121 | let sectionData = self.sectionDatas.proxy[section] as! SectionData 122 | let rowCount = sectionData.rowDatas.proxy.count 123 | 124 | let indexes = _pickRandom(0.. 0) 147 | 148 | let sectionCount = self.sectionDatas.proxy.count 149 | 150 | let indexes = _pickRandom(0.. 0) 160 | 161 | let sectionCount = self.sectionDatas.proxy.count 162 | 163 | let section = $.random(sectionCount) 164 | let sectionData = self.sectionDatas.proxy[section] as! SectionData 165 | let rowCount = sectionData.rowDatas.proxy.count 166 | 167 | let indexes = _pickRandom(0..*/ 183 | 184 | init(title: String, rowDatas: DynamicArray/**/) 185 | { 186 | self.title = title 187 | self.rowDatas = rowDatas 188 | } 189 | 190 | convenience init(title: String, rowDataArray: [RowData]) 191 | { 192 | self.init(title: title, rowDatas: DynamicArray/**/(rowDataArray)) 193 | } 194 | 195 | subscript(i: Int) -> RowData 196 | { 197 | return self.rowDatas.proxy[i] as! RowData 198 | } 199 | 200 | /// return 1 sectionData with random (0..<3) rowDatas 201 | class func randomData() -> SectionData 202 | { 203 | let dateString = _dateString(NSDate()) 204 | 205 | let rowDatas = Array(0..<$.random(3)) 206 | .map { i -> RowData in 207 | return RowData(title: "\(dateString)-\(i)") 208 | } 209 | 210 | return SectionData(title: "\(dateString)", rowDataArray: rowDatas) 211 | } 212 | 213 | /// return 1 sectionData with 0 rowDatas 214 | class func emptyData() -> SectionData 215 | { 216 | let dateString = _dateString(NSDate()) 217 | 218 | return SectionData(title: "\(dateString)", rowDataArray: []) 219 | } 220 | } 221 | 222 | // inner class 223 | class RowData: NSObject 224 | { 225 | let title: String 226 | 227 | init(title: String) 228 | { 229 | self.title = title 230 | } 231 | 232 | class func randomData() -> RowData 233 | { 234 | let dateString = _dateString(NSDate()) 235 | 236 | return RowData(title: "\(dateString)") 237 | } 238 | } 239 | 240 | // inner enum 241 | enum TableLocation: String, CustomStringConvertible 242 | { 243 | case Section = "Section" 244 | case Row = "Row" 245 | 246 | var description: String 247 | { 248 | return self.rawValue 249 | } 250 | 251 | mutating func toggle() 252 | { 253 | switch self { 254 | case .Section: self = .Row 255 | case .Row: self = .Section 256 | } 257 | } 258 | } 259 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/ButtonViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | 12 | class ButtonViewController: UIViewController 13 | { 14 | @IBOutlet var label: UILabel! 15 | @IBOutlet var button: UIButton! 16 | @IBOutlet var barButtonItem: UIBarButtonItem! 17 | 18 | var buttonStream: Stream? 19 | var barButtonStream: Stream? 20 | 21 | override func viewDidLoad() 22 | { 23 | super.viewDidLoad() 24 | 25 | self._setupButton() 26 | self._setupBarButtonItem() 27 | } 28 | 29 | func _setupButton() 30 | { 31 | // self.buttonStream = self.button?.buttonStream("OK") 32 | self.buttonStream = self.button?.buttonStream { _ in "Button \(arc4random_uniform(UInt32.max))" } 33 | 34 | // REACT: button ~> label 35 | (self.label, "text") <~ self.buttonStream! 36 | 37 | // REACT: button ~> print 38 | ^{ print($0!) } <~ self.buttonStream! 39 | } 40 | 41 | func _setupBarButtonItem() 42 | { 43 | // self.barButtonStream = self.barButtonItem?.stream("OK") 44 | self.barButtonStream = self.barButtonItem?.stream { _ in "BarButton \(arc4random_uniform(UInt32.max))" } 45 | 46 | // REACT: button ~> label 47 | (self.label, "text") <~ self.barButtonStream! 48 | 49 | // REACT: button ~> print 50 | ^{ print($0!) } <~ self.barButtonStream! 51 | } 52 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/ButtonViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/GestureViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GestureViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | 12 | class GestureViewController: UIViewController, UIGestureRecognizerDelegate 13 | { 14 | @IBOutlet var gestures: [UIGestureRecognizer]! 15 | @IBOutlet var label: UILabel! 16 | 17 | var streams: [Stream] = [] 18 | 19 | override func viewDidLoad() 20 | { 21 | super.viewDidLoad() 22 | 23 | for gesture in self.gestures { 24 | 25 | gesture.delegate = self 26 | let gestureClassName = NSStringFromClass(gesture.dynamicType) 27 | 28 | let stream: Stream = gesture.stream { gesture -> String? in 29 | // e.g. UITapGestureRecognizer state=3 (161.0,325.0) 30 | return "\(gestureClassName) state=\(gesture!.state.rawValue) \(gesture!.locationInView(gesture?.view))" 31 | } 32 | 33 | // REACT: gesture ~> print 34 | ^{ print($0!) } <~ stream 35 | 36 | self.streams += [stream] 37 | 38 | } 39 | 40 | // combinedStream (concatenating above stream-strings) 41 | let combinedStream = self.streams 42 | |> merge2All 43 | |> map { (values: [String??], changedValue: String?) -> String? in 44 | 45 | return values 46 | .map { ($0 ?? "")!! } 47 | .filter { !$0.isEmpty } 48 | .joinWithSeparator("\n") 49 | } 50 | 51 | // REACT 52 | (self.label, "text") <~ combinedStream 53 | 54 | self.streams += [combinedStream] 55 | 56 | } 57 | 58 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool 59 | { 60 | return true 61 | } 62 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/GestureViewController.xib: -------------------------------------------------------------------------------- 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 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/IncrementalSearchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IncrementalSearchViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2015/06/01. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | import Alamofire 12 | import SwiftyJSON 13 | 14 | private func _searchUrl(query: String) -> String 15 | { 16 | let escapedQuery = query.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet()) ?? "" 17 | return "https://api.bing.com/osjson.aspx?query=\(escapedQuery)" 18 | } 19 | 20 | private let _reuseIdentifier = "reuseIdentifier" 21 | 22 | class IncrementalSearchViewController: UITableViewController, UISearchBarDelegate 23 | { 24 | var searchController: UISearchController? 25 | var searchResultStream: Stream? 26 | var searchResult: [String]? 27 | 28 | dynamic var searchText: String = "" 29 | 30 | override func viewDidLoad() 31 | { 32 | super.viewDidLoad() 33 | 34 | let searchController = UISearchController(searchResultsController: nil) 35 | searchController.dimsBackgroundDuringPresentation = false 36 | searchController.searchBar.delegate = self 37 | 38 | self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: _reuseIdentifier) 39 | self.tableView.tableHeaderView = searchController.searchBar 40 | 41 | // http://useyourloaf.com/blog/2015/04/26/search-bar-not-showing-without-a-scope-bar.html 42 | searchController.searchBar.sizeToFit() 43 | 44 | self.searchController = searchController 45 | 46 | self.searchResultStream = KVO.stream(self, "searchText") 47 | // |> peek(print) 48 | |> debounce(0.15) 49 | |> map { ($0 as? String) ?? "" } // map to Equatable String for `distinctUntilChanged()` 50 | |> distinctUntilChanged 51 | |> map { query -> Stream in 52 | let request = Alamofire.request(.GET, _searchUrl(query), parameters: nil, encoding: .URL) 53 | return Stream.fromTask(_requestTask(request)) 54 | } 55 | |> switchLatestInner 56 | 57 | // REACT 58 | self.searchResultStream! ~> { print($0) } 59 | 60 | // REACT 61 | self.searchResultStream! ~> { [weak self] json in 62 | self?.searchResult = json[1].arrayValue.map { $0.stringValue } 63 | self?.tableView.reloadData() 64 | } 65 | } 66 | 67 | // MARK: - UISearchBarDelegate 68 | 69 | func searchBar(searchBar: UISearchBar, textDidChange searchText: String) 70 | { 71 | self.searchText = searchText 72 | } 73 | 74 | // MARK: - UITableViewDataSource 75 | 76 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int 77 | { 78 | return 1 79 | } 80 | 81 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 82 | { 83 | return self.searchResult?.count ?? 0 84 | } 85 | 86 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 87 | { 88 | let cell = tableView.dequeueReusableCellWithIdentifier(_reuseIdentifier, forIndexPath: indexPath) 89 | 90 | cell.textLabel?.text = self.searchResult?[indexPath.row] 91 | 92 | return cell 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/MultipleTextFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultipleTextFieldViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | 12 | private let MIN_PASSWORD_LENGTH = 4 13 | 14 | /// 15 | /// Original demo: 16 | /// iOS - ReactiveCocoaをかじってみた - Qiita 17 | /// http://qiita.com/paming/items/9ac189ab0fe5b25fe722 18 | /// 19 | class MultipleTextFieldViewController: UIViewController 20 | { 21 | @IBOutlet var usernameTextField: UITextField! 22 | @IBOutlet var emailTextField: UITextField! 23 | @IBOutlet var passwordTextField: UITextField! 24 | @IBOutlet var password2TextField: UITextField! 25 | 26 | @IBOutlet var messageLabel: UILabel! 27 | @IBOutlet var okButton: UIButton! 28 | 29 | var buttonEnablingStream: Stream? 30 | var buttonEnablingStream2: Stream<[AnyObject?]>? 31 | var errorMessagingStream: Stream? 32 | var buttonTappedStream: Stream? 33 | 34 | override func viewDidLoad() 35 | { 36 | super.viewDidLoad() 37 | 38 | self._setupViews() 39 | self._setupStreams() 40 | } 41 | 42 | func _setupViews() 43 | { 44 | self.messageLabel.text = "" 45 | self.okButton.enabled = false 46 | } 47 | 48 | func _setupStreams() 49 | { 50 | //-------------------------------------------------- 51 | // Create Streams 52 | //-------------------------------------------------- 53 | 54 | let usernameTextStream = self.usernameTextField.textChangedStream() 55 | let emailTextStream = self.emailTextField.textChangedStream() 56 | let passwordTextStream = self.passwordTextField.textChangedStream() 57 | let password2TextStream = self.password2TextField.textChangedStream() 58 | 59 | let combinedTextStream = [usernameTextStream, emailTextStream, passwordTextStream, password2TextStream] 60 | |> merge2All 61 | 62 | // create button-enabling stream via any textField change 63 | self.buttonEnablingStream = combinedTextStream 64 | |> map { (values, changedValue) -> NSNumber? in 65 | 66 | let username = (values[0] ?? nil) ?? "" 67 | let email = (values[1] ?? nil) ?? "" 68 | let password = (values[2] ?? nil) ?? "" 69 | let password2 = (values[3] ?? nil) ?? "" 70 | 71 | print("username=\(username), email=\(email), password=\(password), password2=\(password2)") 72 | 73 | // validation 74 | let buttonEnabled = username.characters.count > 0 && email.characters.count > 0 && password.characters.count >= MIN_PASSWORD_LENGTH && password == password2 75 | 76 | print("buttonEnabled = \(buttonEnabled)") 77 | 78 | return NSNumber(bool: buttonEnabled) // NOTE: use NSNumber because KVO does not understand Bool 79 | } 80 | 81 | // create error-messaging stream via any textField change 82 | self.errorMessagingStream = combinedTextStream 83 | |> map { (values, changedValue) -> String? in 84 | 85 | let username = (values[0] ?? nil) ?? "" 86 | let email = (values[1] ?? nil) ?? "" 87 | let password = (values[2] ?? nil) ?? "" 88 | let password2 = (values[3] ?? nil) ?? "" 89 | 90 | if username.characters.count <= 0 { 91 | return "Username is not set." 92 | } 93 | else if email.characters.count <= 0 { 94 | return "Email is not set." 95 | } 96 | else if password.characters.count < MIN_PASSWORD_LENGTH { 97 | return "Password requires at least \(MIN_PASSWORD_LENGTH) characters." 98 | } 99 | else if password != password2 { 100 | return "Password is not same." 101 | } 102 | 103 | return nil 104 | } 105 | 106 | // create button-tapped stream via okButton 107 | self.buttonTappedStream = self.okButton.buttonStream("OK") 108 | 109 | //-------------------------------------------------- 110 | // Stream callbacks on finished 111 | //-------------------------------------------------- 112 | 113 | self.buttonEnablingStream?.then { value, errorInfo -> Void in 114 | print("buttonEnablingStream finished") 115 | } 116 | self.errorMessagingStream?.then { value, errorInfo -> Void in 117 | print("errorMessagingStream finished") 118 | } 119 | self.buttonTappedStream?.then { value, errorInfo -> Void in 120 | print("buttonTappedStream finished") 121 | } 122 | 123 | //-------------------------------------------------- 124 | // Bind & React to Streams 125 | //-------------------------------------------------- 126 | 127 | // REACT: enable/disable okButton 128 | (self.okButton, "enabled") <~ self.buttonEnablingStream! 129 | 130 | // REACT: update error-message 131 | (self.messageLabel, "text") <~ self.errorMessagingStream! 132 | 133 | // REACT: button tap 134 | self.buttonTappedStream! ~> { [weak self] (value: String?) -> Void in 135 | if let self_ = self { 136 | if value == "OK" { 137 | // release all streams when receiving "OK" stream 138 | self_.buttonEnablingStream = nil 139 | self_.errorMessagingStream = nil 140 | self_.buttonTappedStream = nil 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/MultipleTextFieldViewController.xib: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 71 | 77 | 86 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/TextFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | 12 | class TextFieldViewController: UIViewController 13 | { 14 | @IBOutlet var label: UILabel! 15 | @IBOutlet var throttleLabel: UILabel! 16 | @IBOutlet var debounceLabel: UILabel! 17 | @IBOutlet var textField: UITextField! 18 | 19 | var stream: Stream? 20 | var throttleStream: Stream? 21 | var debounceStream: Stream? 22 | 23 | override func viewDidLoad() 24 | { 25 | super.viewDidLoad() 26 | 27 | self.stream = self.textField?.textChangedStream() 28 | self.throttleStream = self.stream! 29 | |> throttle(1) 30 | |> map { text -> String? in "\(text!) (throttled)" } 31 | self.debounceStream = self.stream! 32 | |> debounce(1) 33 | |> map { text -> String? in "\(text!) (debounced)" } 34 | 35 | // REACT: textField ~> label 36 | (self.label, "text") <~ self.stream! 37 | 38 | // REACT: textField ~> throttleLabel 39 | (self.throttleLabel, "text") <~ self.throttleStream! 40 | 41 | // REACT: textField ~> debounceLabel 42 | (self.debounceLabel, "text") <~ self.debounceStream! 43 | 44 | // // REACT: textField ~> print 45 | // ^{ print($0!) } <~ self.stream! 46 | } 47 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/TextFieldViewController.xib: -------------------------------------------------------------------------------- 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 | 29 | 35 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/TimerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimerViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2014/10/06. 6 | // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | 12 | class TimerViewController: UIViewController 13 | { 14 | @IBOutlet var label: UILabel! 15 | @IBOutlet var pauseResumeButton: UIButton! 16 | @IBOutlet var cancelButton: UIButton! 17 | 18 | var stream: Stream? 19 | 20 | override func viewDidLoad() 21 | { 22 | super.viewDidLoad() 23 | 24 | // NOTE: use class method (no need to create NSTimer-instance) 25 | self.stream = NSTimer.stream(timeInterval: 1) { (sender: NSTimer?) -> String? in 26 | return "\(NSDate())" 27 | } 28 | 29 | // REACT: button ~> label 30 | (self.label, "text") <~ self.stream! 31 | 32 | // REACT: button ~> print 33 | ^{ print($0!) } <~ self.stream! 34 | } 35 | 36 | override func viewDidDisappear(animated: Bool) 37 | { 38 | super.viewDidDisappear(animated) 39 | 40 | switch self.stream!.state { 41 | case .Cancelled: 42 | break 43 | default: 44 | print("") 45 | print("NOTE: TimerViewController is not deinited yet (due to iOS8-UISplitViewController's behavior) so timer-stream is still alive.") 46 | print("") 47 | } 48 | } 49 | 50 | // use IBAction instead of ReactKit.Stream for this tutorial 51 | @IBAction func handlePauseResumeButton(sender: AnyObject) 52 | { 53 | let button = sender as! UIButton 54 | 55 | switch self.stream!.state { 56 | case .Paused: 57 | self.stream?.resume() 58 | button.setTitle("Pause", forState: .Normal) 59 | case .Running: 60 | self.stream?.pause() 61 | button.setTitle("Resume", forState: .Normal) 62 | default: 63 | print("Do nothing (timer-stream is already cancelled)") 64 | break 65 | } 66 | 67 | } 68 | 69 | @IBAction func handleCancelButton(sender: AnyObject) 70 | { 71 | self.stream?.cancel() 72 | } 73 | } -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/TimerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 40 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/WhoToFollowViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhoToFollowViewController.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2015/01/05. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | import SwiftTask 12 | 13 | import Alamofire 14 | import SwiftyJSON 15 | import Haneke 16 | 17 | /// 18 | /// Original demo: 19 | /// 20 | /// - The introduction to Reactive Programming you've been missing" 21 | /// https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) 22 | /// 23 | /// - "Who to follow" Demo (JavaScript) 24 | /// http://jsfiddle.net/staltz/8jFJH/48/ 25 | /// 26 | class WhoToFollowViewController: UIViewController { 27 | 28 | @IBOutlet var user1Button: UIButton! 29 | @IBOutlet var user2Button: UIButton! 30 | @IBOutlet var user3Button: UIButton! 31 | @IBOutlet var refreshButton: UIButton! 32 | 33 | override func viewDidLoad() 34 | { 35 | super.viewDidLoad() 36 | 37 | self._setupButtons() 38 | } 39 | 40 | func _setupButtons() 41 | { 42 | let refreshButtonStream: Stream = self.refreshButton!.buttonStream("refresh") 43 | 44 | let user1ButtonStream = self.user1Button!.buttonStream(1) 45 | let user2ButtonStream = self.user2Button!.buttonStream(2) 46 | let user3ButtonStream = self.user3Button!.buttonStream(3) 47 | 48 | /// refreshButton -> random URL -> get JSON 49 | let jsonStream = refreshButtonStream 50 | |> startWith("refresh on start") 51 | |> map { _ -> Alamofire.Request in 52 | let since = Int(arc4random_uniform(500)) 53 | return Alamofire.request(.GET, "https://api.github.com/users", parameters: ["since" : since], encoding: .URL) 54 | } 55 | |> flatMap { Stream.fromTask(_requestTask($0)) } 56 | 57 | typealias UserDict = [String : AnyObject] 58 | 59 | func createRandomUserStream(userButtonStream: Stream) -> Stream 60 | { 61 | let streams: [Stream] = [ 62 | userButtonStream 63 | |> map { $0 as Any } 64 | |> startWith("clear"), 65 | jsonStream 66 | |> map { $0 as Any } 67 | ] 68 | return streams |> combineLatestAll 69 | |> map { values -> UserDict? in 70 | 71 | if let json = values.last as? SwiftyJSON.JSON { 72 | let randomIndex = Int(arc4random_uniform(UInt32(json.count))) 73 | return json[randomIndex].dictionaryObject ?? nil 74 | } 75 | else { 76 | return nil 77 | } 78 | } 79 | |> merge(refreshButtonStream |> map { _ in nil }) 80 | 81 | } 82 | let randomUser1Stream = createRandomUserStream(user1ButtonStream) 83 | let randomUser2Stream = createRandomUserStream(user2ButtonStream) 84 | let randomUser3Stream = createRandomUserStream(user3ButtonStream) 85 | 86 | // OWNED: retain streams by `self` (convenient method in replace of `self.retainingStreams += [myStream]`) 87 | randomUser1Stream.ownedBy(self) 88 | randomUser2Stream.ownedBy(self) 89 | randomUser3Stream.ownedBy(self) 90 | 91 | //-------------------------------------------------- 92 | // Render 93 | //-------------------------------------------------- 94 | 95 | func renderUserButton(userButton: UIButton?, userDict: UserDict?) 96 | { 97 | userButton?.setTitle(userDict?["login"] as? String, forState: .Normal) 98 | userButton?.setImage(nil, forState: .Normal) 99 | 100 | // resize & update avatar using HanekeSwift 101 | if let avatarURLString = userDict?["avatar_url"] as? String { 102 | let avatarURL = NSURL(string: avatarURLString) 103 | if let avatarURL = avatarURL { 104 | userButton?.hnk_setImageFromURL(avatarURL, state: .Normal, placeholder: nil, format: nil, failure: nil, success: nil) 105 | } 106 | } 107 | 108 | } 109 | 110 | // REACT: userButton ~> re-render 111 | randomUser1Stream ~> { [weak self] userDict in 112 | renderUserButton(self?.user1Button, userDict: userDict) 113 | } 114 | randomUser2Stream ~> { [weak self] userDict in 115 | renderUserButton(self?.user2Button, userDict: userDict) 116 | } 117 | randomUser3Stream ~> { [weak self] userDict in 118 | renderUserButton(self?.user3Button, userDict: userDict) 119 | } 120 | 121 | } 122 | 123 | 124 | } 125 | -------------------------------------------------------------------------------- /ReactKitCatalog-iOS/Samples/WhoToFollowViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 32 | 44 | 56 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /ReactKitCatalog.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Shared/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // ReactKitCatalog 4 | // 5 | // Created by Yasuhiro Inami on 2015/03/21. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dollar 11 | import SwiftTask 12 | import Alamofire 13 | import SwiftyJSON 14 | import Async 15 | 16 | // pick `count` random elements from `sequence` 17 | func _pickRandom(sequence: S, _ count: Int) -> [S.Generator.Element] 18 | { 19 | var array = Array(sequence) 20 | var pickedArray = Array() 21 | 22 | for _ in 0.. String 38 | { 39 | return dateFormatter.stringFromDate(date) 40 | } 41 | 42 | enum CatalogError: String, ErrorType 43 | { 44 | case InvalidArgument 45 | } 46 | 47 | /// analogous to JavaScript's `$.getJSON(requestUrl)` using Alamofire & SwiftyJSON 48 | func _requestTask(request: Alamofire.Request) -> Task 49 | { 50 | guard let urlString = request.request?.URLString else { 51 | return Task(error: CatalogError.InvalidArgument) 52 | } 53 | 54 | return Task { fulfill, reject in 55 | 56 | print("request to \(urlString)") 57 | 58 | request.responseJSON { response in 59 | 60 | print("response from \(urlString)") 61 | 62 | if let error = response.result.error { 63 | reject(error) 64 | return 65 | } 66 | 67 | Async.background { 68 | let json = JSON(response.result.value!) 69 | 70 | Async.main { 71 | fulfill(json) 72 | } 73 | } 74 | 75 | } 76 | return 77 | } 78 | } --------------------------------------------------------------------------------