1
2

More than 1 year has passed since last update.

WidgetKitで天気予報アプリ作ってみた〜位置情報取得&保存編〜

Last updated at Posted at 2022-01-31

投稿の経緯

前回投稿したWidgetKitで天気予報アプリ作ってみた~天気情報取得編~の続編です。
今回は位置情報を取得して保存するところまでを記事にしようと思います。

前回の記事を見てない方は先に↓こちら↓を確認してください。

開発環境

Swift 5.5
Xcode 13.2.1

サンプルプロジェクト

GitHubにPushしています。気になる方はご覧ください。
https://github.com/ken-sasaki-222/WeatherWidget
QR_615642.png

位置情報取得

今回はウィジェットの開発がメインなので、位置情報の取得はあまり作り込まずに進めたいと思います。

CoreLocationを追加

f1d514039f04c23c7a3d6fc74ef01941.png
Targets > Build Phases > Link Binaries with LibrariesにCoreLocationを追加。

Info.plistへ追加

ff1ce19d65839fd746c585eac081cc16.png
Praivacy - Location When In Use Usage Descriptionを追加してアラートに表示するテキストを設定します。

AppDelegateを追加

WeatherWidgetApp.swift
import SwiftUI

@main
struct WeatherWidgetApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
AppDelegate.swift
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クラスを追加

LocationManagerHelper.swift
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

UserDefaultsDataStore.swift
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

UserRepositoryInterface.swift
import Foundation

protocol UserRepositoryInterface {
    var lat: Double { get set }
    var lng: Double { get set }
}
UserRepository.swift
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クラスを書き換える

RepositoryRocator.swift
import Foundation

class RepositoryRocator {
    
    static func getWeatherRepository() -> WeatherRepositoryInterface {
        WeatherRepository()
    }
    
    static func getUserRepository() -> UserRepositoryInterface {
        UserRepository()
    }
}
LocationManagerHelper.swift
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を取得しています。位置情報が更新されたタイミングで緯度経度を保存するように書き換えました。

保存した位置情報でリクエストを送る

WeatherViewModel.swift
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でご依頼お待ちしております!
QR_615427.png
↓活動リンクはこちら↓
https://linktr.ee/sasaki.ken

1
2
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
2