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


今回必要な各バージョン

nodenpmcordovaionic
v6.13.15.7.16.5.02.2.3
v8.10.05.7.16.5.03.2.0
v8.11.36.4.17.1.04.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。以上です

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)

とりあえずAPIへリクエス

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?
}

apiリクエス

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でエラーになるケースが多く、キャリア回線に変更すると成功するケースが多いようだ。エラーメッセージに回線の切り替えを促す等の対応も考えられる。ただ、確実にエラーを回避する対応方法は現時点では見つかっておらず、SDKSafari View Controllerを使用したブラウザログイン(LineSDKAPI.startWebLoginメソッド)で逃げ道を作っておくしかなさそう。以上です

Swift UITextField textFieldShouldReturnが呼ばれない

UITextFieldDelegateもちゃんと実装したのになぜか呼ばれなくてハマった

func textFieldShouldReturn(textField: UITextField) -> Bool {
  return true
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  return true
}

昔のソース貼り付けたら動かなくて、微妙にかわったのか。。以上です