Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sahara-ooga/a0de1555c0e3b1e35c30a72edd913f88 to your computer and use it in GitHub Desktop.
Save sahara-ooga/a0de1555c0e3b1e35c30a72edd913f88 to your computer and use it in GitHub Desktop.
Playground codes which search github repository using RxSwift
//: [Previous](@previous)
import UIKit
import RxSwift
import RxCocoa
import PlaygroundSupport
// Definition
struct GithubRepository {
let name: String
let starCount: Int
init(from dictionary: [String: Any]) {
name = dictionary["full_name"] as! String
starCount = dictionary["stargazers_count"] as! Int
}
}
struct SearchResult {
let repos: [GithubRepository]
let totalCount: Int
init?(from response: Any) {
guard let response = response as? [String: Any],
let reposDictionaries = response["items"] as? [[String: Any]],
let count = response["total_count"] as? Int else { return nil }
repos = reposDictionaries.compactMap({ GithubRepository(from: $0) })
totalCount = count
}
}
func searchRepos(keyword: String) -> Observable<SearchResult?> {
let endPoint = "https://api.github.com"
let path = "/search/repositories"
let query = "?q=\(keyword)"
let url = URL(string: endPoint + path + query)!
let request = URLRequest(url: url)
return URLSession.shared.rx
.json(request: request)
.map { SearchResult(from: $0) }
}
class SearchViewController: UIViewController {
let searchField = UITextField()
let totalCountLabel = UILabel()
let reposLabel = UILabel()
let disposeBag = DisposeBag() //Disposableを保持しておいて、deinitされるタイミングで購読の破棄を一気に行う
//ViewControllerが破棄されるタイミングで購読もすべて破棄される
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
setupSubviews()
bind()
}
func setupSubviews() {
searchField.frame = CGRect(x: 10, y: 10, width: 300, height: 20)
totalCountLabel.frame = CGRect(x: 10, y: 40, width: 300, height: 20)
reposLabel.frame = CGRect(x: 10, y: 60, width: 300, height: 500)
searchField.borderStyle = .roundedRect
searchField.keyboardType = .alphabet
reposLabel.numberOfLines = 0
[searchField, totalCountLabel, reposLabel]
.forEach(view.addSubview(_:))
}
}
extension SearchViewController {
func bind() {
let result: Observable<SearchResult?> = searchField.rx.text
.orEmpty//Optional<String> -> String or ""
.asObservable()// テキストフィールドの値が更新されるたびに、最新の値を排出する
.skip(1)//最初に排出する値は無視する
.debounce(0.3, scheduler: MainScheduler.instance)//直前の値から0.3秒以内に排出された次の値は無視
.distinctUntilChanged()//直前の値と同じなら無視する
.flatMapLatest{ searchRepos(keyword: $0)//新しい値だけの結果にする
.observeOn(MainScheduler.instance)//以降の処理はメインスレッドで行う指定
.catchErrorJustReturn(nil)//もしもエラーが排出された場合はnilを返す
//エラーが排出されてもObservableシーケンスに値が流れ続けるようになる
}.share(replay: 1)//connectable Observableへの変換
// SearchResultからStringへの変換
let foundRepos: Observable<String> = result.map {
let repos = $0?.repos ?? [GithubRepository]()
return repos.reduce("") {
$0 + "\($1.name)(\($1.starCount))\n"
}
}
// SearchResultからStringへの変換
let foundCount: Observable<String> = result.map {
let count = $0?.totalCount ?? 0
return "TotalCount: \(count)"
}
//UIパーツへの束縛処理
foundRepos
.bind(to: reposLabel.rx.text)//レポジトリの名前とスター数の文字列をreposLabel.rx.textにバインドする
.disposed(by: disposeBag)
foundCount
.startWith("Input Repository Name")//検索結果から生成する文字列が渡ってくるまで表示する値を指定
.bind(to: totalCountLabel.rx.text)//検索結果数をtotalCountLabel.rx.textにバインドする
.disposed(by: disposeBag)
}
}
//PlaygroundPage.current.liveView = SearchViewController()
let parent = UIViewController()
let vc = SearchViewController()
vc.view.layoutIfNeeded()
parent.view.translatesAutoresizingMaskIntoConstraints = false
vc.view.translatesAutoresizingMaskIntoConstraints = false
parent.addChildViewController(vc)
parent.view.addSubview(vc.view)
NSLayoutConstraint.activate([
vc.view.leadingAnchor.constraint(equalTo: parent.view.leadingAnchor),
vc.view.trailingAnchor.constraint(equalTo: parent.view.trailingAnchor),
vc.view.topAnchor.constraint(equalTo: parent.view.topAnchor),
vc.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor)
]
)
let traits = UITraitCollection(
traitsFrom: [
UITraitCollection(verticalSizeClass: .compact),
UITraitCollection(horizontalSizeClass: .compact),
UITraitCollection(preferredContentSizeCategory: .extraExtraExtraLarge)
]
)
parent.setOverrideTraitCollection(traits, forChildViewController: vc)
parent.preferredContentSize = parent.view.frame.size
PlaygroundPage.current.liveView = parent
//: [Next](@next)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment