はじめに
趣味でSwiftを書いている@KaitoKudouです。
これまでSwiftUIは学習したことがなく、なんとなくやってみようと思い、SwiftUIを使って外部APIを叩いてみようと思いました。どうせならやったことないCombineフレームワークもちょっとかじってみました。(ほんのちょっとだけ)
デモアプリ概要
今回はConnpass APIを使用してListでイベントを表示する簡易的なデモアプリを作りました。
先に完成版をお見せします。
今回作成のプロジェクトはGitHubに上げています。
https://github.com/KaitoKudou/SwiftUI_Combine
モデルを定義
レスポンスを見ながらモデルを書きます。
import Foundation
struct ConnpassGeneral: Codable {
let events: [Event]
}
struct Event: Codable, Hashable, Identifiable {
let id: Int
let title: String
let eventUrl: String
enum CodingKeys: String, CodingKey {
case id = "event_id"
case title = "title"
case eventUrl = "event_url"
}
}
通信部分
import Combine
import Foundation
class ConnpassTopViewModel: ObservableObject {
var disposable = Set<AnyCancellable>()
@Published var connpassGeneral: ConnpassGeneral?
@Published var eventData: [Event]?
init() {
fetchConpassEvents()
}
func fetchConpassEvents() {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let url = URL(string: "https://connpass.com/api/v1/event/?keyword=Swift&order=3")!
let request = URLRequest(url: url)
URLSession.shared.dataTaskPublisher(for: request)
.map({ (data, response) in
return data
})
.decode(type: ConnpassGeneral.self, decoder: decoder)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("error : " + error.localizedDescription)
case .finished:
print("----------success-----------")
}
}, receiveValue: { [weak self] connpassGeneral in
self?.eventData = connpassGeneral.events
print(self?.eventData as Any)
})
.store(in: &disposable)
}
}
List部分
UIKitでいうUITableViewにあたる部分です。
import SwiftUI
struct ConnpassTopView: View {
@ObservedObject var connpassTopViewModel = ConnpassTopViewModel()
var body: some View {
NavigationView {
List(connpassTopViewModel.eventData ?? [Event.init(id: 0, title: "hoge", eventUrl: "hoge")]) { event in
NavigationLink(
destination: ConnpassEventDetailView(eventData: event)) {
ConnpassEventRowView(eventData: event)
}
}
.navigationTitle("Connpassイベント")
}
.onAppear(perform: {
self.connpassTopViewModel.fetchConpassEvents()
})
}
}
struct ConnpassTopView_Previews: PreviewProvider {
static var previews: some View {
ConnpassTopView(connpassTopViewModel: ConnpassTopViewModel())
}
}
セルを押したときの遷移先
今回はセルを押したらSFSafariViewControllerを使ってSafariへ遷移するようにしました。しかし、SFSafariViewControllerはUIKitで使えるクラスになります。SwiftUIのViewでUIKitを使うには UIViewControllerRepresentableプロトコルに準拠すると使えます。
UIViewControllerRepresentableプロトコルでは、makeUIViewController(context:)とupdateUIViewController(_:context:)が必須なメソッドとなります。
import SafariServices
import SwiftUI
struct ConnpassEventDetailView: UIViewControllerRepresentable {
var eventData: Event
typealias UIViewControllerType = SFSafariViewController
func makeUIViewController(context: Context) -> SFSafariViewController {
let url = URL(string: eventData.eventUrl)
return SFSafariViewController(url: url!)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
}
}
struct ConnpassEventDetailView_Previews: PreviewProvider {
static var eventData = ConnpassTopViewModel().eventData
static var previews: some View {
ConnpassEventDetailView(eventData: eventData?[0] ?? Event.init(id: 0, title: "hoge", eventUrl: "hoge"))
}
}
最後に
全体的に少ないコード量で済みました。SwiftUIはUIKitに比べると記述量が減り、書きやすいと思いました。まだ私はUIKitの方が実装にかかる時間が短いですが、SwiftUIに慣れたら開発が爆速で進むと思いました。Combineに関してはまだまだ知識が足りないので精進します...
既存のライブラリもSwiftUIに対応したものは少なく、まだしばらくはUIKitを使った開発が主流なのかなと思いました。