GitLab リポジトリ削除手順
たまにやろうとすると忘れるのでメモ
英語
1. プロジェクトのTOPへ遷移
2. 左メニューの「Settings」をクリック
3. Settings画面の「Advanced」(画面1番下)の「Expand」をクリック
4. 展開された画面の「Remove project」をクリック
5. 確認画面が表示されるのでプロジェクト名を入力して「Confirm」クリック
日本語
1. プロジェクトのTOPへ遷移
2. 左メニューの「設定」をクリック
3. 設定画面の「Advanced」(画面1番下)の「Expand」をクリック
4. 展開された画面の「Remove project」をクリック
5. 確認画面が表示されるのでプロジェクト名を入力して「Confirm」クリック
以上です
ndenv 使い方メモ
今さらだけどプロジェクトごとにnodeやら各ソフトのバージョンがバラバラなのが増えて来てnodebrewでいちいち切り替えるのもきつくなってきた。のでndenvいれたのでそのメモ。
ndenvインストール
すでにインストールされていないか確認してなければインストール
# 確認 $ brew list | grep ndenv # インストール $ brew install ndenv # インストールされたことを確認 $ ndenv --version ndenv 0.4.0-4-ga339097
.bash_profileにndenvの設定を追記
自分の場合はnodebrewを使っていたのでnodebrewの設定をコメントアウト
# nodebrew # export PATH=$HOME/.nodebrew/current/bin:$PATH # ndenv export PATH="$HOME/.ndenv/bin:$PATH" eval "$(ndenv init -)"
nodejsのPATHがndenvになっているか確認
$ source ~/.bash_profile $ which node /Users/xxxxx/.ndenv/shims/node
ndenv installを使えるように
$ git clone https://github.com/riywo/node-build.git $(ndenv root)/plugins/node-build
参考
https://qiita.com/noraworld/items/462689e108c10102d51f
今回必要な各バージョン
node | npm | cordova | ionic |
---|---|---|---|
v6.13.1 | 5.7.1 | 6.5.0 | 2.2.3 |
v8.10.0 | 5.7.1 | 6.5.0 | 3.2.0 |
v8.11.3 | 6.4.1 | 7.1.0 | 4.2.1 |
前準備
## インストール可能なバージョンを調べる $ ndenv install -l ## インストール済みのバージョンを調べる $ ndenv versions
v8.10.0の環境作成
## プロジェクト用のディレクトリ作成 $ mkdir v8_10_0 && cd $_ ## node v8.10.0のインストール $ ndenv install v8.10.0 ## このプロジェクトで使用するnodeのバージョン設定 $ ndenv local v8.10.0 $ node -v v8.10.0 ## npm 5.7.1をインストール $ npm install -g npm@5.7.1 $ npm -v 5.7.1 ## cordova 6.5.0をインストール $ npm install -g cordova@6.5.0 $ cordova -v 6.5.0 ## ionic 3.2.0をインストール $ npm install -g ionic@3.2.0 $ ionic -v 3.2.0
v6.13.1とv8.11.3についてもそれぞれ行う。
プロジェクトのディレクトリ直下に.node-versionがそれぞれ作成されるのでこのファイルをgitなどで管理して共有すればok。以上です
Sequel Pro SQLの履歴
メニューの「表示」>「コンソール」から確認できた。
以前探してた時はわからなかったのだがあったのか。以上です
Swift Dateで日付のみのリスト作成
大した話じゃないけどDateFormatterは入力と出力でそれぞれ用意すると考えるとなんとなくわかりやすいと思った
start = "2018-01-01" end = "2018-01-31" // 入力文字列のフォーマット var inputFormatter = DateFormatter() inputFormatter.dateFormat = "yyyy-MM-dd" // String → Date変換 let startDate = inputFormatter.date(from: start) let endDate = inputFormatter.date(from: end) // 出力文字列のフォーマット let outputFormatter = DateFormatter() outputFormatter.dateFormat = "yyyy-MM-dd" // 1日ずつ進めて配列に追加 var items = [String]() var target = start while target <= end { items.append(outputFormatter.string(from: target)) target = Date(timeInterval: 86400, since: target) }
以上です
今週のお題「読書の秋」
Swift Alamofire + Codableでapiリクエストでの最低限必要そうな処理まとめ
はじめに
アプリ開発でWebAPIにリクエストしてレスポンスを画面に表示したりデータを更新したりありがちな処理をまとめておく。
やりたいことは主に以下
・APIへリクエスト(Get、Post、Patch、Delete)
・APIのレスポンスからCodableなクラスのインスタンスを作成
・レスポンスヘッダー(認証情報等)を取得
・リクエストヘッダー(認証情報等)を設定してリクエスト
・ファイルのアップロード
・エラー処理
・まとめ
APIへリクエスト(Get、Post、Patch、Delete)
let url = "http://localhost:3000/path/to" let method = HTTPMethod.get // .post or .patch or .delete let parameters:[String:Any] = [ "param": "xxxxx", "param2": xxxxx ] Alamofire.request(url, method: method, parameters: parameters) .responseJSON { response in response.result.value /* Optional({ user = { id = 1, name = "Tom" comment = nil icon = { main = "http://xxxxx" sub = nil } } }) */ }
APIのレスポンスからCodableなクラスのインスタンスを作成
上のような形で取得した値をそのままディクショナリとして使うと中身を都度キャストしたり扱いずらい。そこで予めCodableなクラスを定義しておくと簡単に扱える。
Model.swift
class User: Codable { let id: Int let name: String let comment: String? // 任意な項目 let icon: Icon // 別のCodableなクラス } class Icon: Codable { let main: String? let sub: String? }
Alamofire.request(url, method: method, parameters: parameters) .response { response in guard let data = response.data else { return } let decoder = JSONDecoder() let user = try? decoder.decode(User.self, from: data) /* Optional({ id: 1 name: "Tom" comment: nil icon: { main: "http://xxxxx" sub: nil } }) */ /* 簡単に扱える!! user.id、user.name、user.icon.main */ } }
レスポンスヘッダー(認証情報等)を取得
ログインAPIへリクエストして成功したら認証情報がレスポンスヘッダーにセットされて返ってくるので以降のリクエストはリクエストヘッダーに認証情報をセットしてリクエストする。みたいなのはよくある実装だと思うのでそれを$$
class Auth: Codable { token: String } Alamofire.request(url, method: method, parameters: parameters) .response { response in let decoder = JSONDecoder() let auth = try? decoder.decode(type, from: JSON(response.response?.allHeaderFields as Any).rawData()) /* Optional({ token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }) */ }
リクエストヘッダー(認証情報等)を設定してリクエスト
取得した認証情報をリクエストヘッダーに設定してリクエスト。といってもheadersにセットするだけ。
let headers = [ "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ] Alamofire.request(url, method: method, parameters: parameters, headers: headers) .response { response in }
ファイルのアップロード
ファイルアップロードは今までのrequestメソッドではなくuploadメソッドを使う。
let fileUrl = URL("file://path/to") // ファイルのパス let fileKey = "upload_file" // パラメータ名 Alamofire.upload(multipartFormData:{ multipartFormData in // アップロードファイルをセット multipartFormData.append(fileUrl, withName: fileKey) // その他のパラメータをセット(Dataに変換して1つずつセットしていく) parameters?.keys.forEach({ (key) in if let data = convertData(value: parameters![key]!) { multipartFormData.append(data, withName: key) } }) }, usingThreshold:UInt64.init(), to: url, method: .post, headers: headers encodingCompletion: { result in // }) func convertData(value: AnyObject?) -> Data? { if let string = value as? String { return string.data(using: .utf8) } else if let int = value as? Int { return String(int).data(using: .utf8) } else if let bool = value as? Bool { return String(bool ? 1 : 0).data(using: .utf8) } else { return nil } }
エラー処理
httpステータスコードが200番台じゃなかったらエラーと判定。validateメソッドで条件を指定する
Alamofire.request(url, method: method, parameters: parameters) .validate(statusCode: 200..<300) .response { response in if response.error != nil { // エラー } }
まとめ
API共通クラス
import Foundation import Alamofire import SwiftyJSON import UIKit class ApiManager { // ログイン static func login<T : Codable>(url:String, type:T.Type, params:[String: Any]? = nil, success:((T?)->())? = nil, fail:((Error?)->())? = nil) { Alamofire.request(url, method: .post, parameters: params) .validate(statusCode: 200..<300) .response { response in if response.error != nil { fail?(response.error) } else { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase success?(try? decoder.decode(type, from: JSON(response.response?.allHeaderFields as Any).rawData())) } } } // ファイルUPLOAD static func upload<T : Codable>(token: String, url:String, type:T.Type, fileKey: String, fileUrl: URL, params:[String: AnyObject?]? = nil, success:((T?)->())? = nil, fail:((Error?)->())? = nil) { Alamofire.upload(multipartFormData:{ multipartFormData in multipartFormData.append(fileUrl, withName: fileKey) params?.keys.forEach({ (key) in if let data = ApiManger.convertData(value: params![key]!) { multipartFormData.append(data, withName: key) } }) }, usingThreshold:UInt64.init(), to: url, method: .post, headers: [ "token": token ], encodingCompletion: { result in switch result { case .success(let upload, _, _): upload.validate(statusCode: 200..<300).responseData(completionHandler: { response in if response.error != nil { fail?(response.error) } else { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase success?(try? decoder.decode(type, from: response.data!)) } }) case .failure(let error): fail?(error) } }) } static func convertData(value: AnyObject?) -> Data? { if let string = value as? String { return string.data(using: .utf8) } else if let int = value as? Int { return String(int).data(using: .utf8) } else if let bool = value as? Bool { return String(bool ? 1 : 0).data(using: .utf8) } else { return nil } } static func request<T : Codable>(token: String, url:String, method:HTTPMethod, type:T.Type, params:[String: Any]? = nil, success:((T?)->())? = nil, fail:((Error?)->())? = nil) { Alamofire.request(url, method: method, parameters: params, headers: [ "token": token ]) .validate(statusCode: 200..<300) .response { response in if response.error == nil { let decoder = JSONDecoder() let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" decoder.dateDecodingStrategy = .formatted(formatter) decoder.keyDecodingStrategy = .convertFromSnakeCase success?(try? decoder.decode(type, from: response.data!)) } else if let code = response.response?.statusCode { fail?(response.error) } }
// 呼び出す
let url = "http://xxxx/path/to let token = "xxxxxxxxxxxxxxxxxxxxxxxxxx" let params = [ "hoge": "fuga" ] ApiProvider.patch(token: token, url: url, type: User.self, params: params ,success: { user in // success }, fail: { error in // error or fail })
ずらずら書いたけどこんな感じで共通化しておく。Codableの機能でDate型への対応とパラメータ名がスネークケースの場合にもキャメルケースに対応することができる。というのを上にも入れてみた。ダメだ。。力尽きて相変わらず適当になった。。以上です。
iOS12でLine SDK Loginエラー
https://developers.line.me/ja/docs/ios-sdk/
LINEが提供しているこちらのSDKを使ってLINEログインを自分のアプリに組み込んでいるのだが、iOS12でSDKでログインに失敗するケースが多発。LINEブログやその他のLINE製アプリでも確認したがエラーになることが判明
エラー内容
Error Domain=LineSDKServerErrorDomain Code=0 "Authentication API Error" UserInfo={NSLocalizedDescription=Authentication API Error, NSUnderlyingError=0x28397db30{ ErrorDomain=NSURLErrorDomain Code=-1005 "ネットワーク接続が切れました。" UserInfo={NSUnderlyingError=0x28397f930 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x281426620 [0x1b7b555f0]>{length = 16, capacity = 16, bytes = 0x100201bbcb6899400000000000000000}, _kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=https://api.line.me/v2/oauth/accessToken, NSErrorFailingURLKey=https://api.line.me/v2/oauth/accessToken, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-4, NSLocalizedDescription=ネットワーク接続が切れました
wifiでエラーになるケースが多く、キャリア回線に変更すると成功するケースが多いようだ。エラーメッセージに回線の切り替えを促す等の対応も考えられる。ただ、確実にエラーを回避する対応方法は現時点では見つかっておらず、SDKでSafari View Controllerを使用したブラウザログイン(LineSDKAPI.startWebLoginメソッド)で逃げ道を作っておくしかなさそう。以上です
Swift UITextField textFieldShouldReturnが呼ばれない
UITextFieldDelegateもちゃんと実装したのになぜか呼ばれなくてハマった
誤
func textFieldShouldReturn(textField: UITextField) -> Bool { return true }
正
func textFieldShouldReturn(_ textField: UITextField) -> Bool { return true }
昔のソース貼り付けたら動かなくて、微妙にかわったのか。。以上です