Skip to content

Commit 42739f0

Browse files
committed
Add Full functional app for RxSwift+APIKit+Himotoki (test included)
1 parent 3bb3c9e commit 42739f0

File tree

163 files changed

+14449
-1272
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

163 files changed

+14449
-1272
lines changed

Example/APIKitExt.xcodeproj/project.pbxproj

+247-7
Large diffs are not rendered by default.
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>$(EXECUTABLE_NAME)</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>$(PRODUCT_NAME)</string>
15+
<key>CFBundlePackageType</key>
16+
<string>BNDL</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleVersion</key>
20+
<string>1</string>
21+
</dict>
22+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Himotoki_Sample_Tests.swift
3+
// Himotoki Sample Tests
4+
//
5+
// Created by DTVD on 1/9/17.
6+
// Copyright © 2017 CocoaPods. All rights reserved.
7+
//
8+
9+
import XCTest
10+
import RxSwift
11+
import RxTest
12+
13+
@testable import Himotoki_Sample
14+
15+
class RepoViewModelTests: XCTestCase {
16+
let scheduler = TestScheduler(initialClock: 0)
17+
let diposeBag = DisposeBag()
18+
var viewModel: RepoViewModel!
19+
20+
override func setUp() {
21+
class MockGithubApiService: GithubApiService {
22+
func getRepoList(query: String) -> Observable<[Repo]> {
23+
return Observable.of([])
24+
}
25+
}
26+
class ViewModelForTest: RepoViewModel {
27+
override var githubApiService: GithubApiService {
28+
return MockGithubApiService()
29+
}
30+
}
31+
viewModel = ViewModelForTest()
32+
super.setUp()
33+
}
34+
35+
override func tearDown() {
36+
super.tearDown()
37+
}
38+
39+
func testRepoViewModel() {
40+
let query = scheduler.createHotObservable([
41+
next(10, "111"),
42+
next(30, "333"),
43+
next(40, "")
44+
])
45+
viewModel.receive(query.asObservable())
46+
47+
let repoCount = scheduler.createObserver(Int.self)
48+
let diposable = viewModel.repos
49+
.map{$0.count}
50+
.asObservable()
51+
.bindTo(repoCount)
52+
scheduler.scheduleAt(100000, action: {diposable.dispose()})
53+
scheduler.start()
54+
55+
let correctMessages: [Recorded<Event<Int>>] = [
56+
next(10, 0),
57+
next(30, 0)
58+
]
59+
60+
XCTAssertEqual(repoCount.events, correctMessages)
61+
62+
}
63+
64+
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,59 @@
1-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vJo-8D-tPO">
3+
<device id="retina4_7" orientation="portrait">
4+
<adaptation id="fullscreen"/>
5+
</device>
36
<dependencies>
4-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
7+
<deployment identifier="iOS"/>
8+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
59
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
610
</dependencies>
711
<scenes>
8-
<!--View Controller-->
9-
<scene sceneID="tne-QT-ifu">
12+
<!--Repo View Controller-->
13+
<scene sceneID="40B-VL-Gba">
1014
<objects>
11-
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
12-
<layoutGuides>
13-
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
14-
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
15-
</layoutGuides>
16-
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
15+
<tableViewController id="wdL-Iu-bYN" customClass="RepoViewController" customModule="Himotoki_Sample" customModuleProvider="target" sceneMemberID="viewController">
16+
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="60" sectionHeaderHeight="28" sectionFooterHeight="28" id="daR-eX-glA">
1717
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
1818
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19-
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
20-
</view>
21-
</viewController>
22-
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
19+
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
20+
<prototypes>
21+
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="RepoCell" rowHeight="60" id="LQH-Is-Zhk" userLabel="Repo Cell" customClass="RepoTableViewCell" customModule="Himotoki_Sample" customModuleProvider="target">
22+
<rect key="frame" x="0.0" y="28" width="375" height="60"/>
23+
<autoresizingMask key="autoresizingMask"/>
24+
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="LQH-Is-Zhk" id="ooV-LV-i83">
25+
<rect key="frame" x="0.0" y="0.0" width="375" height="59"/>
26+
<autoresizingMask key="autoresizingMask"/>
27+
</tableViewCellContentView>
28+
</tableViewCell>
29+
</prototypes>
30+
</tableView>
31+
<navigationItem key="navigationItem" id="sng-Ka-Wa5"/>
32+
<connections>
33+
<outlet property="tableView" destination="daR-eX-glA" id="sBp-qp-Z2U"/>
34+
</connections>
35+
</tableViewController>
36+
<placeholder placeholderIdentifier="IBFirstResponder" id="TOn-2Z-orO" userLabel="First Responder" sceneMemberID="firstResponder"/>
2337
</objects>
38+
<point key="canvasLocation" x="1004" y="-167.76611694152925"/>
39+
</scene>
40+
<!--Navigation Controller-->
41+
<scene sceneID="MTb-J4-Bel">
42+
<objects>
43+
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="vJo-8D-tPO" sceneMemberID="viewController">
44+
<toolbarItems/>
45+
<navigationBar key="navigationBar" contentMode="scaleToFill" id="Lnt-rC-4El">
46+
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
47+
<autoresizingMask key="autoresizingMask"/>
48+
</navigationBar>
49+
<nil name="viewControllers"/>
50+
<connections>
51+
<segue destination="wdL-Iu-bYN" kind="relationship" relationship="rootViewController" id="Wvx-Ke-znn"/>
52+
</connections>
53+
</navigationController>
54+
<placeholder placeholderIdentifier="IBFirstResponder" id="KJX-vg-Rth" userLabel="First Responder" sceneMemberID="firstResponder"/>
55+
</objects>
56+
<point key="canvasLocation" x="64.799999999999997" y="-167.76611694152925"/>
2457
</scene>
2558
</scenes>
2659
</document>

Example/Himotoki Sample/Model/GithubAPIService.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ import APIKit
1111
import APIKitExt
1212
import RxSwift
1313

14-
class GithubApiService {
14+
protocol GithubApiService {
15+
func getRepoList(query: String) -> Observable<[Repo]>
16+
}
17+
18+
protocol UsesGithubApiService {
19+
var githubApiService: GithubApiService { get }
20+
}
21+
22+
class MixInGithubApiService: GithubApiService {
1523
let schedulers = SchedulerService.sharedInstance
1624

1725
// Normal
@@ -31,5 +39,8 @@ class GithubApiService {
3139
func getRepoList(query: String) -> Observable<[Repo]>{
3240
let request = RepoRequest(query: query)
3341
return Session.send(request: request)
42+
.subscribeOn(schedulers.backgroundWorkScheduler)
43+
.observeOn(schedulers.mainScheduler)
3444
}
3545
}
46+

Example/Himotoki Sample/Model/Repo.swift

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Himotoki
1212
struct Repo {
1313
var id: Int
1414
var fullName: String
15+
var avatar: String
1516
var stargazersCount: Int
1617
}
1718

@@ -20,6 +21,7 @@ extension Repo: Decodable {
2021
return try Repo(
2122
id: e <| "id",
2223
fullName: e <| "full_name",
24+
avatar: e <| ["owner", "avatar_url"],
2325
stargazersCount: e <| "stargazers_count"
2426
)
2527
}

Example/Himotoki Sample/Repo.xib

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
3+
<device id="retina4_7" orientation="portrait">
4+
<adaptation id="fullscreen"/>
5+
</device>
6+
<dependencies>
7+
<deployment identifier="iOS"/>
8+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
9+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+
</dependencies>
11+
<objects>
12+
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
13+
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
14+
<view contentMode="scaleToFill" id="CLV-y6-dzj" customClass="RepoTableViewCell" customModule="Himotoki_Sample" customModuleProvider="target">
15+
<rect key="frame" x="0.0" y="0.0" width="400" height="60"/>
16+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17+
<subviews>
18+
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Repo name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oOm-Nc-TLA">
19+
<rect key="frame" x="90" y="20" width="170" height="21"/>
20+
<constraints>
21+
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="170" id="peY-F6-FGk"/>
22+
</constraints>
23+
<fontDescription key="fontDescription" type="system" pointSize="17"/>
24+
<nil key="textColor"/>
25+
<nil key="highlightedColor"/>
26+
</label>
27+
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="stars" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fro-yn-WEv">
28+
<rect key="frame" x="280" y="20" width="100" height="21"/>
29+
<fontDescription key="fontDescription" type="system" pointSize="17"/>
30+
<nil key="textColor"/>
31+
<nil key="highlightedColor"/>
32+
</label>
33+
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1Dn-pR-gmK" userLabel="Avatar">
34+
<rect key="frame" x="20" y="5" width="50" height="50"/>
35+
<constraints>
36+
<constraint firstAttribute="width" constant="50" id="3eE-Lx-Hcy"/>
37+
<constraint firstAttribute="height" constant="50" id="ywf-Pm-q2d"/>
38+
</constraints>
39+
</imageView>
40+
</subviews>
41+
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
42+
<constraints>
43+
<constraint firstItem="Fro-yn-WEv" firstAttribute="centerY" secondItem="CLV-y6-dzj" secondAttribute="centerY" id="0eT-fb-Yot"/>
44+
<constraint firstItem="1Dn-pR-gmK" firstAttribute="centerY" secondItem="CLV-y6-dzj" secondAttribute="centerY" id="9Rr-tI-U0g"/>
45+
<constraint firstItem="oOm-Nc-TLA" firstAttribute="centerY" secondItem="CLV-y6-dzj" secondAttribute="centerY" id="apC-gr-IE6"/>
46+
<constraint firstItem="1Dn-pR-gmK" firstAttribute="leading" secondItem="CLV-y6-dzj" secondAttribute="leading" constant="20" id="hDc-gD-f6y"/>
47+
<constraint firstItem="Fro-yn-WEv" firstAttribute="trailing" secondItem="CLV-y6-dzj" secondAttribute="trailing" constant="-20" id="k9Q-71-zuZ"/>
48+
<constraint firstItem="oOm-Nc-TLA" firstAttribute="leading" secondItem="1Dn-pR-gmK" secondAttribute="trailing" constant="20" id="oMl-W4-Whj"/>
49+
<constraint firstItem="Fro-yn-WEv" firstAttribute="leading" secondItem="oOm-Nc-TLA" secondAttribute="trailing" constant="20" id="xMB-Ry-MDZ"/>
50+
</constraints>
51+
<nil key="simulatedStatusBarMetrics"/>
52+
<nil key="simulatedTopBarMetrics"/>
53+
<nil key="simulatedBottomBarMetrics"/>
54+
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
55+
<connections>
56+
<outlet property="avatar" destination="1Dn-pR-gmK" id="fmQ-Qd-EU8"/>
57+
<outlet property="name" destination="oOm-Nc-TLA" id="Znv-DL-3eA"/>
58+
<outlet property="stargazersCount" destination="Fro-yn-WEv" id="6JL-6J-OQO"/>
59+
</connections>
60+
</view>
61+
</objects>
62+
</document>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// RepoTableViewCell.swift
3+
// APIKitExt
4+
//
5+
// Created by DTVD on 1/9/17.
6+
// Copyright © 2017 CocoaPods. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
class RepoTableViewCell: UITableViewCell {
12+
13+
@IBOutlet weak var name: UILabel!
14+
@IBOutlet weak var stargazersCount: UILabel!
15+
@IBOutlet weak var avatar: UIImageView!
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// RepoViewController.swift
3+
// Himotoki Sample
4+
//
5+
// Created by DTVD on 12/24/16.
6+
// Copyright © 2016 CocoaPods. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import RxSwift
11+
import RxCocoa
12+
13+
class RepoViewController: UIViewController {
14+
15+
@IBOutlet var tableView: UITableView!
16+
var searchBar = UISearchBar()
17+
fileprivate let reuseIdentifier = "RepoCell"
18+
fileprivate var viewModel = RepoViewModel()
19+
fileprivate let diposeBag = DisposeBag()
20+
fileprivate let veryLightGray = UIColor(
21+
red:200/255,
22+
green:200/255,
23+
blue:206/255,
24+
alpha:1
25+
)
26+
27+
override func viewDidLoad() {
28+
super.viewDidLoad()
29+
searchBar.delegate = self
30+
self.navigationItem.titleView = searchBar
31+
tableView.register( UINib(nibName: "Repo", bundle: nil) , forCellReuseIdentifier: reuseIdentifier)
32+
rxBinding()
33+
}
34+
35+
func rxBinding() {
36+
let query = searchBar.rx.text
37+
.orEmpty
38+
.throttle(0.5, scheduler: MainScheduler.instance)
39+
.distinctUntilChanged()
40+
41+
viewModel.receive(query)
42+
43+
viewModel.repos
44+
.asDriver(onErrorJustReturn: [])
45+
.drive(tableView.rx.items(cellIdentifier: reuseIdentifier, cellType: RepoTableViewCell.self)) { _, repo, cell in
46+
cell.name.text = repo.fullName
47+
let stars = repo.stargazersCount
48+
cell.stargazersCount.text = String(describing: stars) + " stars"
49+
let url = repo.avatar
50+
cell.avatar.downloadFrom(url: url)
51+
}
52+
.addDisposableTo(diposeBag)
53+
}
54+
}
55+
56+
extension RepoViewController: UISearchBarDelegate {
57+
58+
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
59+
self.navigationController?.navigationBar.barTintColor = veryLightGray
60+
searchBar.setShowsCancelButton(true, animated: true)
61+
return true
62+
}
63+
64+
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
65+
searchBar.text = ""
66+
searchBar.setShowsCancelButton(false, animated: true)
67+
searchBar.resignFirstResponder()
68+
self.navigationController?.navigationBar.barTintColor = nil
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// RepoViewModel.swift
3+
// APIKitExt
4+
//
5+
// Created by DTVD on 1/9/17.
6+
// Copyright © 2017 CocoaPods. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import RxSwift
11+
import APIKit
12+
13+
class RepoViewModel {
14+
15+
var githubApiService: GithubApiService { return MixInGithubApiService() }
16+
var repos: Observable<[Repo]> = Observable.empty()
17+
var schedulers = SchedulerService.sharedInstance
18+
19+
func receive(_ query: Observable<String>) {
20+
let result = query
21+
.filter{!$0.isEmpty}
22+
.flatMapLatest{ [unowned self] query -> Observable<[Repo]> in
23+
return self.githubApiService.getRepoList(query: query)
24+
}
25+
.shareReplay(1)
26+
repos = result
27+
}
28+
}

0 commit comments

Comments
 (0)