Blog
◀ November 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ▶
December 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
◀ January 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ▶
Swift Playgrouds 4で遊んでみた!
2021-12-22T18:00:00+09:00
コーディングの学習/補助ツールとして人気のSwift Playgrounds。今年のWWDCで予告されていたiPad上でアプリが開発できてしまうツールへの大幅アップデート「Swift Playgrounds 4」がつい先日、正式リリースされました。アプリ開発の敷居はどこまで下がるのか、間口は広がるのか、早速を試してみました!
画面は「コード(ファイル)一覧」「エディタ」「プレビュー」という3カラム構成で、「コード一覧」と「プレビュー」はどちらか一方のみ表示(もしくは非表示)されます……そう、たったこれだけです。
誰でも簡単にアプリ開発というと、アプリの画面を構成する部品をドラッグ&ドロップで配置できたり、視覚的に訴える設定画面でプロパティが設定できたりするノーコードのツールが思い浮かびますよね。XcodeでのStoryboard(古くはInterface Builder)を用いたアプリ開発その方向でした。ところが、SwiftUIでの開発を前提としたSwift Playgroundsは、アプリの画面設計からプロパティの設定まですべてがコードに帰着するため、コードのスニペットを挿入するボタンはあれど、ノーコードのツールにありがちな設定画面はありません。
「コードは難しいもの」が根底にあるノーコードのツールは、アプリ開発の自由度を高めるには設定画面の項目を増やす必要があるため、(提供元が)少しでも気を抜くとツール自体がどんどん複雑化して「コードを書かずに簡単に使えるツール」から「コードは書かないけれど難しくて使えないツール」へ変容してしまいます。一方で「コードは学べば誰でも書ける」前提のSwift Playgroundsは、表面的な取っ付きやすさを潔く捨ててツール自体は極度に簡素化されており、コードが書ける/学ぶ一歩を踏み出した人にとってのわかりやすさを追求しているように感じました。
さて、画面を眺めていても仕方ないので、何か作ってみましょう。
簡単すぎず難しすぎず、空き時間で作るに手頃な難易度のお題として、今回参加しているアドベントカレンダーのアプリを作ってみることにしました。
アドベントカレンダーのページを開くと、カレンダー形式で参加者が、一覧形式で記事が表示されています。ページのソースコードを確認すると、RSSフィードがあるようなのでこれを元データにしようとしましたが、参加者のアイコン画像が含まれていません。
アプリの画面は寂しくなりますが、トライアルなのでそれでも良いかと思いつつさらにソースコードを眺めていると、scriptタグの中にページの表示に使っている元データがJSON(JSのオブジェクト)形式で出力されているのを見付けました。値が変数化されている部分があり、多少加工が必要そうですが、これを利用しない手はありませんw
まずはデータ取得部分を作ってみます。なお、アプリの全体的な構成は「SwiftUI Tutorials」を踏襲しているのでそちらを参照ください。
final class ModelData: ObservableObject { ⋮ let (data, _) = try await URLSession.shared.data(from: URL(string: "https://adventar.org/calendars/6792")!) guard let source = String(data: data, encoding: .utf8) else { return } let regex = try NSRegularExpression(pattern: "window\\.__NUXT__=\\(function\\(a,b,c,d,e,f\\)\\{return (.+)\\}\\((\".+\"),(\".+\"),(\".+\"),(\".+\"),(.+),(.+)\\)\\);", options: [.dotMatchesLineSeparators, .anchorsMatchLines]) guard let match = regex.firstMatch(in: source, range: NSRange(location: 0, length: source.count)) else { return } let startIndex = source.index(source.startIndex, offsetBy: match.range(at: 1).location) let endIndex = source.index(startIndex, offsetBy: match.range(at: 1).length) var json = String(source[startIndex..<endIndex]) for i in 2...7 { let startIndex = source.index(source.startIndex, offsetBy: match.range(at: i).location) let endIndex = source.index(startIndex, offsetBy: match.range(at: i).length) json = json.replacingOccurrences(of: ":\(String(UnicodeScalar(95 + i)!))([,|}])", with: ":\(source[startIndex..<endIndex].replacingOccurrences(of: "\\", with: "\\\\"))$1", options: [.regularExpression]) }
ページのソースコード(HTML)を取得してJSON(JSのオブジェクト)形式のデータを抽出しているところ。ガチガチの正規表現なので該当部分に変更が入ったら終わりです。我ながらひどいコードですが、こういうことするの嫌いじゃありませんw
キーが文字列ではないため、JSONDecoderのallowsJSON5を有効にします。
do { let decoder = JSONDecoder() decoder.allowsJSON5 = true let adventar = try decoder.decode(Adventar.self, from: json.data(using: .utf8)!)
JSONそのままの構造のモデルが出来上がりました。
struct Adventar: Hashable, Codable { var layout: String var data: [Data] struct Data: Hashable, Codable { var calendar: Calendar struct Calendar: Hashable, Codable { var id: String var owner: Owner var title: String var description: String var year: Int var entries: [Entry] struct Owner: Hashable, Codable { var id: String var name: String var iconUrl: String } struct Entry: Hashable, Codable { var id: String var owner: Owner? var day: Int var comment: String? var url: String? var title: String? var imageUrl: String? } } } }
カレンダー形式の画面はLazyVGridを使えば簡単です。
struct CalendarView: View { ⋮ ScrollView(.vertical) { LazyVGrid(columns: Array(repeating: GridItem(spacing: 1, alignment: .top), count: 7), spacing: 1) { ⋮ } .clipShape(RoundedRectangle(cornerRadius: 8)) .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) }
iOS 15からAsyncImageなんていう便利な部品が使えるようになっていたので、画像はこれで読み込みます。
struct CalendarCell: View { ⋮ AsyncImage(url: URL(string: entry.owner?.iconUrl ?? "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=")!) { image in image .resizable() .aspectRatio(contentMode: .fit) } placeholder: { ProgressView() } .clipShape(Circle())
記事はウェブビュー(SFSafariViewController)で表示しますが、そのままではSwiftUIで扱えないため、UIViewControlleRepresentableでラップします。
struct SafariView: UIViewControllerRepresentable { var url: URL func makeUIViewController(context: Context) -> some UIViewController { let safariViewController = SFSafariViewController(url: url) safariViewController.dismissButtonStyle = .close return safariViewController } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } }
あとは全体的なレイアウトを整えて、細かな装飾を加えれば完成です!
本当にiPadだけでアプリが開発できてしまいましたw
小さな画面内をやり繰りしての開発作業は快適とは言い難いのに加え、最初のリリース直後だからか、(特にビューの)1ファイルあたりの行数が100行程度に収まるように気を付けないとコードの補完やプレビューがまともに動作しなくなり、最終的にはアプリが落ちてしまうこともありましたが、初学者が気軽にアプリ開発してみる環境としてはもちろん、その気になれば(Swiftパッケージで事足りるのであれば)本格的なアプリ開発も不可能ではなさそうです。
今日で上場して丸1年が経ちました。
夜な夜な開発していた頃から足掛け10年、気付いたら開発チームは60人規模になっていました。元々はたった1人で開発(※Androidアプリを除く)してきたので、開発チームを作ること=自分がやっていた仕事を切り出してより専門的な能力の高い人たちに任せていくことでした。
誰かに何かを「任せる」のと「放り投げる」のは違います。任せると言いながら放り投げたあげく、ほらやっぱり自分がいないとね!としていたのでは何も変わりません(自尊心は満たされるかもしれませんがw
元来、何かを作るときは「自分の作品」を作っている感覚が強く、自分の中で完成したものを他人にいじくり回されるのが嫌だったりするのですが、当然そんなこと言っていられるわけもなく、自分がいなくても滞りなく開発が進み、さらに良いものを開発し続けられる体制の構築をこれまで注意深く進めてきました。
そして今年初め、遂に開発本部長の役割を譲り渡すまでに至りました!
もちろん譲り渡したから終わり……ではなく、新体制への移行を多方面で支援しているわけですが、自由に使える時間も多少増え、それが「UPDATE2021」アプリやその表現力をさらに強化した野心的なプロトタイプの開発に繋がりました。
また、今期のヤプリクでとあるアイデアの検討をしていたとき、自分が言おうとしたのとまったく同じ、ヤプリらしい実現方法が相次いで開発メンバーから提案される場面がありました。言語化するのが難しい感覚的なところまでしっかり継承できているのを目の当たりにして感慨深くもありましたw
しかしながら、組織図上での明確な役割がなくなったことから、時には「最近何をしているのかがわからない最たる人」といった心無い言葉も浴びせられるようになり、今年後半からは新たに「佐野研究室」なる箱で、新規事業の「種」となる開発に取り組み始めました。
振り返ってみると、今は自分しかできない/自分じゃなきゃ!と思えることでも勇気を持って手放したら、さらにおもしろい何かが自然と舞い込んでくる、それを改めて強く感じた1年でした。
来年もその流れば続きそうなので、「佐野研究室」の今後に乞うご期待!w