LoginSignup
1
0

More than 1 year has passed since last update.

【Swift】CombineDataSourcesを使ってみた

Last updated at Posted at 2022-11-22

はじめに

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(セクションなし)

Simulator Screen Recording - iPhone 14 - 2022-11-22 at 15.59.09.gif

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(セクションあり)

Simulator Screen Recording - iPhone 14 - 2022-11-22 at 16.01.00.gif

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

Simulator Screen Recording - iPhone 14 - 2022-11-22 at 16.07.10.gif

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年前から更新がないのでちょっと心配です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0