投稿の経緯
前回投稿したWidgetKitで天気予報アプリ作ってみた~天気情報取得編~の続編です。
今回は位置情報を取得して保存するところまでを記事にしようと思います。
前回の記事を見てない方は先に↓こちら↓を確認してください。
開発環境
Swift 5.5
Xcode 13.2.1
サンプルプロジェクト
GitHubにPushしています。気になる方はご覧ください。
https://github.com/ken-sasaki-222/WeatherWidget
位置情報取得
今回はウィジェットの開発がメインなので、位置情報の取得はあまり作り込まずに進めたいと思います。
CoreLocationを追加
Targets > Build Phases > Link Binaries with LibrariesにCoreLocationを追加。
Info.plistへ追加
Praivacy - Location When In Use Usage Description
を追加してアラートに表示するテキストを設定します。
AppDelegateを追加
import SwiftUI
@main
struct WeatherWidgetApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
class AppDelegate: UIResponder, UIApplicationDelegate {
private let locationManagerHelper = LocationManagerHelper()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
locationManagerHelper.callLocationManager()
return true
}
}
@UIApplicationDelegateAdaptor
を使ってAppDelegate
を用意します。今回はアプリ起動時に位置情報を選択してない場合、位置情報許諾アラートを表示するぐらいの実装でいいので、didFinishLaunchingWithOptions
の中で位置情報を扱うHelperクラスを呼びます。
位置情報を扱うHelperクラスを追加
import CoreLocation
class LocationManagerHelper: NSObject, CLLocationManagerDelegate {
private var locationManager: CLLocationManager
override init() {
self.locationManager = CLLocationManager()
super.init()
self.locationManager.delegate = self
}
func callLocationManager() {
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let newLocation = locations.last else {
return
}
let location = CLLocationCoordinate2D(
latitude: newLocation.coordinate.latitude,
longitude: newLocation.coordinate.longitude
)
print("緯度:", location.latitude, "経度:", location.longitude)
locationManager.stopUpdatingLocation()
}
}
位置情報を取得する処理です。先に記載したように、今回はLocationManagerに関する処理をあまり作り込んでいませんので、Helperクラスを用意してその中に位置情報を扱う処理を逃しています。
続いて、取得した位置情報をUserDefaults
に保存していきます。
位置情報保存
WidgetKitで天気予報アプリ作ってみた〜天気情報取得編〜で書いているように今回はアーキテクチャにリポジトリパターンを採用しています。UserDefaultsとのやりとりはRepositoryを経由してDataStoreに任せようと思います。
DataStore
import Foundation
final class UserDefaultsDataStore {
private enum DefaultsKey: String {
case lat
case lng
}
private var defaults: UserDefaults {
UserDefaults.standard
}
var lat: Double {
get {
defaults.double(forKey: DefaultsKey.lat.rawValue)
}
set(newValue) {
defaults.set(newValue, forKey: DefaultsKey.lat.rawValue)
}
}
var lng: Double {
get {
defaults.double(forKey: DefaultsKey.lng.rawValue)
}
set(newValue) {
defaults.set(newValue, forKey: DefaultsKey.lng.rawValue)
}
}
}
UserDefaultsとやりとりをするDataStoreです。緯度経度の保存と取り出しを担当し、取り出した値をRepositoryへ返します。
Repository
import Foundation
protocol UserRepositoryInterface {
var lat: Double { get set }
var lng: Double { get set }
}
import Foundation
class UserRepository: UserRepositoryInterface {
private let userDefaultsDataStore = UserDefaultsDataStore()
var lat: Double {
get {
userDefaultsDataStore.lat
}
set(newValue) {
userDefaultsDataStore.lat = newValue
}
}
var lng: Double {
get {
userDefaultsDataStore.lng
}
set(newValue) {
userDefaultsDataStore.lng = newValue
}
}
}
UserDefaultsDataStoreとやりとりをするRepositoryです。緯度経度の取得と保存は必ずこのRepositoryを経由します。
Helperクラスを書き換える
import Foundation
class RepositoryRocator {
static func getWeatherRepository() -> WeatherRepositoryInterface {
WeatherRepository()
}
static func getUserRepository() -> UserRepositoryInterface {
UserRepository()
}
}
import CoreLocation
class LocationManagerHelper: NSObject, CLLocationManagerDelegate {
private var userRepository: UserRepositoryInterface
private var locationManager: CLLocationManager
init(userRepository: UserRepositoryInterface) {
self.userRepository = userRepository
self.locationManager = CLLocationManager()
super.init()
self.locationManager.delegate = self
}
override convenience init() {
self.init(userRepository: RepositoryRocator.getUserRepository())
}
func callLocationManager() {
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let newLocation = locations.last else {
return
}
let location = CLLocationCoordinate2D(
latitude: newLocation.coordinate.latitude,
longitude: newLocation.coordinate.longitude
)
print("緯度:", location.latitude, "経度:", location.longitude)
userRepository.lat = location.latitude
userRepository.lng = location.longitude
locationManager.stopUpdatingLocation()
}
}
RepositoryLocatorを経由してUserRepositoryを取得しています。位置情報が更新されたタイミングで緯度経度を保存するように書き換えました。
保存した位置情報でリクエストを送る
import Foundation
class WeatherViewModel: NSObject {
private let weatherRepository: WeatherRepositoryInterface
private let userRepository: UserRepositoryInterface
init(weatherRepository: WeatherRepositoryInterface, userRepository: UserRepositoryInterface) {
self.weatherRepository = weatherRepository
self.userRepository = userRepository
super.init()
}
override convenience init() {
self.init(weatherRepository: RepositoryRocator.getWeatherRepository(), userRepository: RepositoryRocator.getUserRepository())
}
func createRequestModel() -> WeatherRequestModel {
let requestModel = WeatherRequestModel(
lat: userRepository.lat,
lng: userRepository.lng
)
return requestModel
}
func fetchWeathers() async {
do {
let response = try await weatherRepository.fetchWeathers(requestModel: createRequestModel())
print("Success fetch weathers:", response.hourly)
}
catch {
print("Error fetch weathers:", error)
}
}
}
WidgetKitで天気予報アプリ作ってみた〜天気情報取得編〜の時点では緯度経度を直接指定していましたが、保存した緯度経度を使ってWeatherRequestModel
を作るように書き換えました。これで現在地から取得した位置情報を使ってリクエストを送れるようになりました。
おわりに
今回はWidgetKitで天気予報アプリ作ってみた~位置情報取得&保存編~について書きました。
次はいよいよウィジェットの開発です!
続きが気になる方は↓こちら↓から
ご覧いただきありがとうございました。
こうしたほうがいいや、ここはちょっと違うなど気になる箇所があった場合、ご教示いただけると幸いです。
お知らせ
現在副業でiOSアプリ開発案件を募集しています。
Twitter DMでご依頼お待ちしております!
↓活動リンクはこちら↓
https://linktr.ee/sasaki.ken