はじめに
UIKitもCombineで書きたいなと思って調べていたら、CombineCocoaやCombineDataSourcesなどを見つけました。
今回はCombineDataSourcesの基本的な使い方を紹介できたらと思います。
データ
GitHubのリポジトリ検索APIを使用します。
Modelは全て一緒なのでここに書いておきます。
SearchRepositories
import Foundation
// MARK: - SearchRepositories
struct SearchRepositories: Codable, Hashable {
var items: [Item]
enum CodingKeys: String, CodingKey {
case items
}
}
// MARK: - Item
struct Item: Codable, Hashable {
let name: String
let owner: Owner
let htmlURL: String
let itemDescription: String?
let languagesURL: String
let stargazersCount: Int
let language: String?
let forksCount: Int
let openIssuesCount: Int
enum CodingKeys: String, CodingKey {
case name
case owner
case htmlURL = "html_url"
case itemDescription = "description"
case languagesURL = "languages_url"
case stargazersCount = "stargazers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
}
}
// MARK: - Owner
struct Owner: Codable, Hashable {
let login: String
let avatarURL: String
let url: String
let htmlURL: String
enum CodingKeys: String, CodingKey {
case login
case avatarURL = "avatar_url"
case url
case htmlURL = "html_url"
}
}
実装
TableView(セクションなし)
View
ViewController
import UIKit
import SnapKit
import Combine
import CombineDataSources
class ViewController: UIViewController {
private let tableView: UITableView = {
let view = UITableView(frame: .zero, style: .grouped)
view.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
return view
}()
private let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
setup()
bind()
}
private func setup() {
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func bind() {
viewModel.data
.bind(subscriber: tableView.rowsSubscriber(cellIdentifier: "UITableViewCell", cellType: UITableViewCell.self, cellConfig: { cell, indexPath, item in
cell.textLabel?.text = item.name
}))
.store(in: &viewModel.subscriptions)
}
}
ViewModel
ViewModel
import Combine
import Foundation
final class ViewModel {
private let url = "https://api.github.com/search/repositories?q=swift"
var data = PassthroughSubject<[Item], Never>()
var subscriptions = Set<AnyCancellable>()
init() {
fetchSearchRepositories()
}
private func fetchSearchRepositories() {
URLSession.shared.dataTaskPublisher(for: URL(string: url)!)
.tryMap { data, _ in return data }
.decode(type: SearchRepositories.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink { complete in
print(complete)
} receiveValue: { [weak self] response in
guard let self else { return }
self.data.send(response.items)
}
.store(in: &subscriptions)
}
}
TableView(セクションあり)
View
ViewController
import UIKit
import SnapKit
import Combine
import CombineDataSources
class ViewController: UIViewController {
private let tableView: UITableView = {
let view = UITableView(frame: .zero, style: .grouped)
view.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
return view
}()
private let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
setup()
bind()
}
private func setup() {
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func bind() {
viewModel.data
.bind(subscriber: tableView.sectionsSubscriber(cellIdentifier: "UITableViewCell", cellType: UITableViewCell.self, cellConfig: { cell, indexPath, item in
cell.textLabel?.text = item.name
}))
.store(in: &viewModel.subscriptions)
}
}
ViewModel
ViewModel
import Combine
import CombineDataSources
import Foundation
final class ViewModel {
private let url = "https://api.github.com/search/repositories?q=swift"
var data = PassthroughSubject<[Section<Item>], Never>()
var subscriptions = Set<AnyCancellable>()
init() {
fetchSearchRepositories()
}
private func fetchSearchRepositories() {
URLSession.shared.dataTaskPublisher(for: URL(string: url)!)
.tryMap { data, _ in return data }
.decode(type: SearchRepositories.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink { complete in
print(complete)
} receiveValue: { [weak self] response in
guard let self else { return }
self.data.send([Section(header: "ヘッダー", items: response.items)])
}
.store(in: &subscriptions)
}
}
CollectionView
View
ViewController
import UIKit
import SnapKit
import Combine
import CombineDataSources
class ViewController: UIViewController {
private let collectionView: UICollectionView = {
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.itemSize = .init(width: 100, height: 100)
collectionViewLayout.sectionInset = .init(top: 10, left: 20, bottom: 10, right: 20)
let view = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
view.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
return view
}()
private let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
setup()
bind()
}
private func setup() {
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func bind() {
viewModel.data
.bind(subscriber: collectionView.itemsSubscriber(cellIdentifier: "UICollectionViewCell", cellType: UICollectionViewCell.self, cellConfig: { cell, indexPath, item in
var config = UIListContentConfiguration.cell()
config.text = item.name
config.secondaryText = item.owner.login
cell.contentConfiguration = config
cell.backgroundColor = .systemGray6
cell.layer.cornerRadius = 10
}))
.store(in: &viewModel.subscriptions)
}
}
ViewModel
ViewModel
import Combine
import CombineDataSources
import Foundation
final class ViewModel {
private let url = "https://api.github.com/search/repositories?q=swift"
var data = PassthroughSubject<[Item], Never>()
var subscriptions = Set<AnyCancellable>()
init() {
fetchSearchRepositories()
}
private func fetchSearchRepositories() {
URLSession.shared.dataTaskPublisher(for: URL(string: url)!)
.tryMap { data, _ in return data }
.decode(type: SearchRepositories.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink { complete in
print(complete)
} receiveValue: { [weak self] response in
guard let self else { return }
self.data.send(response.items)
}
.store(in: &subscriptions)
}
}
おわり
RxSwiftみたいな感じで使えるのでめっちゃいいです!
ただ、3年前から更新がないのでちょっと心配です。