私にとって初のiOSアプリをリリースしたので、App Storeに公開するまでの軌跡を残しておく。
作ったもの
いつでもどこでも世界中の海抜を測れるアプリ『KAIVAP』
使ったもの
-
開発ツール
-
ライブラリ管理ツール
-
フレームワーク
-
API
構築手順
きっかけ
何十本とアプリをリリースしているエンジニアの方にお会いし刺激を受けた。
取り敢えず何でもいいから0→1でアプリをリリースしようと思い、歩いているときに「ここは海抜〇〇mです」と書かれた電柱を見てGoogleMapから海抜(標高)を計測できるアプリを作ろうと決める!
アプリ作成
主要部分はこんな感じ。
100行ぐらいしか書いてないですね笑
import Foundation
import UIKit
import APIKit
import GoogleMaps
import GooglePlaces
class ViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
var locationManager: CLLocationManager!
var placesClient: GMSPlacesClient!
var currentLocation: CLLocationCoordinate2D?
var currentCameraPosition: GMSCameraPosition?
var zoomLevel: Float = 15.0
var mapView: GMSMapView!
// 一部省略
@IBOutlet weak var elevationLabel: UILabel!
@IBOutlet weak var calculateButton: UIButton!
@IBOutlet weak var resetButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
func setupView() {
let camera = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 1.0)
let footerViewHeight: CGFloat = 88.0
let mapViewSize = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height-footerViewHeight)
mapView = GMSMapView.map(withFrame: mapViewSize, camera: camera)
mapView.isMyLocationEnabled = true
do {
mapView.mapStyle = try GMSMapStyle(jsonString: mapStyle)
} catch {
NSLog("One or more of the map styles failed to load. \(error)")
}
mapView.delegate = self
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: -33.86, longitude: 151.20)
marker.title = "Sydney"
marker.snippet = "Australia"
marker.map = mapView
view.addSubview(mapView)
view.sendSubview(toBack: mapView)
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
placesClient = GMSPlacesClient.shared()
}
func calculateElevation(lat: Double, lng: Double) {
let request = GetElevationRequest(lat: lat, lng: lng)
Session.send(request) { [weak self] result in
guard let `self` = self else { return }
switch result {
case .success(let elevation):
if let elevation = elevation.results.first?.elevation {
if elevation == 0 {
self.elevationLabel.textColor = UIColor.init(hex: "CB1B45")
self.elevationLabel.text = "0(計測不能)"
} else {
if elevation > 5 {
self.elevationLabel.textColor = UIColor.init(hex: "333333")
} else {
self.elevationLabel.textColor = UIColor.init(hex: "DB4D6D")
}
let text = NSString(format: "%.1f", elevation)
self.elevationLabel.text = text as String
}
}
case .failure(let error):
print("error: \(error)")
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
currentLocation = location.coordinate
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.animate(to: camera)
} else {
mapView.animate(to: camera)
calculateElevation(lat: location.coordinate.latitude, lng: location.coordinate.longitude)
}
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
currentCameraPosition = position
}
@IBAction func didPressCalculateButton(_ sender: Any) {
if let currentCameraPosition = currentCameraPosition {
calculateElevation(lat: currentCameraPosition.target.latitude, lng: currentCameraPosition.target.longitude)
}
}
@IBAction func didPressResetButton(_ sender: Any) {
if let currentLocation = currentLocation {
self.mapView.animate(toLocation: currentLocation)
self.self.calculateElevation(lat: currentLocation.latitude, lng: currentLocation.longitude)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
実機ビルドが出来ない!?
アプリが完成したので実機で現在地が取れているか確認しようと思い、実機ビルドに挑戦した。
が、シミュレータでは発生しなかったエラーが出現!?!?
Command /bin/sh failed with exit code 1
数日間ハマっていたが、下のダイアログを Xcodeインストール時に拒否していたことが原因だと判明!
Xcodeを再インストールし、パスワードを入力して「常に許可」を選択したらエラーが消えてくれました>_<
Xcodeの「キーチェーンログインのパスワードを入力してください」パスワードが通らない
エラー祭り!?!?
やっと実機ビルドが出来る!!!と思ったら新たなエラーが。。。
今度は何やねん(ーー;)
dyld: Library not loaded: @rpath/Pods_xxx.framework/Pods_xxx
あー。ライブラリーが読み込めていないのかー。意味の分かるエラーで助かるわー。
と思いきや!?!?
そんな優しいエラーではなかった。
Library not loaded エラー?ここを見直そう
Carthageを使ってライブラリを管理する
色々な対処法を試してみたが、、、エラーは一向に消えてくれない。
そこでふと思ったのが、
実機ビルド時のみ起こるってことは証明書周りでミスってる??
確認してみると…
Certificates
もProvisioning Profiles
には何も問題がないんですよ。
キーチェーンアクセスを見ても有効期限
も切れてないし。
数日後…やっと解決策を見つけることが出来ました!
証明書を「常に信頼」から「システムデフォルトを使用」に変更!
dyld: Library not loaded: @rpath/libswiftCore.dylib
どこかのタイミングで変えてしまったんでしょうね。
デフォルトは「システムデフォルトを使用」になっています。
エラー地獄から抜け、やっとの思いで実機ビルドに成功しました!
Archive後のValidateで躓く
iTunes Store Operation Failed
ERROR ITMS-90087: "Unsupported Architectures. The executable for ${APP_NAME}.app/Frameworks/APIKit.framework contains unsupported architectures '[x86_64, i386]'."
Check and Remove Unsupported Architecture [x86_64, i386] in IPA / Archive
上の記事を参考に、サポートされていないアーキテクチャーを外したら上手くいきました!
アプリ公開
アプリを申請してからは比較的順調にリリースまで辿り着けました。
今回一番お世話になった記事を載せておきます。
↓↓↓
iOSアプリを登録、申請して公開するまで
まとめ
プロジェクトを立ち上げてからリリースするまで、
コードを書いている時間 >>> Xcodeの設定に悩まされる時間 だった気がします笑
ですが、
- アプリ申請の流れを把握できた
- Xcodeの設定を見直すことが出来た(次回からは困らない!はず)
- iOSアプリ開発者の仲間入りが出来た
- 証明書周りの知識がほんの少しだけついた
など得るものは大きかったです!
アプリリリースはそんなに難しくない!!!
これからもバンバンiOSアプリをリリースしていきたいと思います!