[iOS] iOS16機能の素振りでシンプルな歩数計アプリをつくった
何をつくったか
シンプルな歩数計アプリを作ってみました。
ソースコードはこちら
開発目的は主に以下です。
- iOS16のロック画面のウィジェットを使ってみたかった
- UICalendarViewを利用してみたかった
- シンプルにViewとModel(データ+ロジック)に分離した設計でアプリを作ってみたかった
利用したiOS16~のもの
2022年9月13日(JST)にiOS16がリリースされ、いくつか新しい機能、APIが提供されるようになりました。今回のアプリではその一部を利用しています。
WidgetKit(ロック画面のウィジェット)
ホームウィジェットの表示は以下のようにsupportedFamilies(_:)
を追加することで実現可能です。
import WidgetKit
import SwiftUI
@main
struct SanpoWidget: Widget {
let kind: String = "Widget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetEntryView(entry: entry)
}
.configurationDisplayName("Sanpo")
.supportedFamilies([.accessoryCircular])
}
}
WeatherKit
WeatherKitは気象データにアクセスできるフレームワークです。
WeatherKitを使用して幅広いデータに基づく有益な気象情報をAppやサービスで提供することにより、ユーザーが最新情報を確認し、身を守り、備えるのをサポートすることができます。iOS 16、iPadOS 16、macOS 13、tvOS 16、watchOS 9向けにはプラットフォーム固有のSwift APIを使用し、その他すべてのプラットフォーム向けにはREST APIを使用することで、AppでWeatherKitを簡単に利用できます。
(引用)WeatherKitの概要 - Apple Developer
今回は現在〜6時間後までの1時間ごとの天気を表示するために利用しました。
下記のように、WeatherService
オブジェクトを介してリクエストすることで対象のデータ(今回の場合は毎時のデータ)が取得できます。
@MainActor
public class WeatherData: ObservableObject {
~~~
@Published public var phase: AsyncStatePhase = .initial
@Published public var hourlyForecasts: Forecast<HourWeather>?
private let service = WeatherService.shared
~~~
private func loadHourlyForecast(for location: CLLocation) async {
phase = .loading
let hourWeather = await Task.detached(priority: .userInitiated) {
let forecast = try? await self.service.weather(
for: location,
including: .hourly
)
return forecast
}.value
hourlyForecasts = hourWeather
phase = .success(Date())
}
}
各データの表示については下記のように先頭から任意の数を取り出し、
if let hourlyForecasts = weatherData.hourlyForecasts {
ForEach(hourlyForecasts[0..<6], id: \.self.date) { forecast in
weatherDataRow(
date: forecast.date,
weatherIconName: forecast.symbolName,
temperature: forecast.temperature,
precipitationChance: forecast.precipitationChance
)
}
.padding(.bottom, 8)
}
個々のデータについてはDate.FormatStyle()
やformatted(_:)
を用いて表示用データに変換しています。
func weatherDataRow(
date: Date,
weatherIconName: String,
temperature: Measurement<UnitTemperature>,
precipitationChance: Double
) -> some View {
HStack {
Text(date, format: Date.FormatStyle().hour(.defaultDigits(amPM: .abbreviated)).minute())
.adaptiveFont(.normal, size: 18)
Spacer()
HStack {
Image(systemName: weatherIconName)
Text(temperature.formatted(.measurement(width: .abbreviated, usage: .weather)))
.adaptiveFont(.normal, size: 18)
.frame(width: 90, alignment: .leading)
}
Spacer()
Text(formattedPrecipitationChance(precipitationChance))
.adaptiveFont(.normal, size: 18)
}
}
データ取得とその表示についてはAppleのサンプルコードもあり、そちらを参考にしています。
Fetching weather forecasts with WeatherKit | Apple Developer Documentation
また、WeatherKitを利用する場合は「Apple Weatherの商標」と、「データソースへの法的リンク」の表示が必要です。
AppでApple Weatherのデータを表示する場合は、WeatherKitドキュメントのアトリビューション要件に従う必要があります。
(引用)App Store Reviewガイドライン - Apple Developer
UICalendarView
iOS16より日付の選択、表示変更が可能なカレンダークラスであるUICalendarView
が利用できます。
今回のアプリでは過去の歩数データを表示するために利用しています。
UICalendarView | Apple Developer Documentation
設計
ViewとModel(Entityとロジックをもつもの)のレイヤーを持つシンプルなものです。
- 歩数などのモデルについてはActive Recordパターンを採用し、ロジックとFactoryメソッドを持ちます。
- 状態管理などのライフサイクルが必要となる場合は
~Data
という命名のclassを作成しています。
Stop using MVVM for SwiftUI | Apple Developer Forums での議論を読みその簡易な実践の意味もあり、MVの設計としています。
そこでの議論ではSwiftの機能を利用し設計をシンプルに保つことを重要視していますが、それは「A Philosophy of Software Design」での内容と通じることが多いように思いました。
「A Philosophy of Software Design」のまとめと感想
おわりに
SwiftUIの登場から数年が経ちますが、Swiftの言語機能の進化に加えて毎年OSの更新があり常に考えることが絶えませんね。そんな訳でついインプットが多くなりがちですが、「触ってみる」ことが「使う」ためには近道です。
シンプルなアプリですが、それを再認識できて良かったです。
誰かの参考になれば幸いです。
Discussion