【C#】【Google Cloud Vision API】拡張

何に使うのかはわからない。
とりあえずSelectManyの繰り返しがひどかったので用意してみた。
何に使うのかはわからない。
ぶっちゃけTextAnnotationの仕様がよくわかってない。。
TextAnnotation.Text.Split('\n')とBlocks.Textが一致してほしいんだけどなぁ。。



using Google.Cloud.Vision.V1;
using System.Linq;

public static class CloudVisionEx
{
    // TextAnnotation → Page → Block → Paragraph → Word → Symbol → Text

    public static Block[] Blocks(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Blocks).ToArray();

    public static Paragraph[] Paragraphs(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Paragraphs()).ToArray();

    public static Paragraph[] Paragraphs(this Page page) => page.Blocks.SelectMany(x => x.Paragraphs).ToArray();

    public static Word[] Words(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Words()).ToArray();

    public static Word[] Words(this Page page) => page.Blocks.SelectMany(x => x.Words()).ToArray();

    public static Word[] Words(this Block block) => block.Paragraphs.SelectMany(x => x.Words).ToArray();

    public static Symbol[] Symbols(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Symbols()).ToArray();

    public static Symbol[] Symbols(this Page page) => page.Blocks.SelectMany(x => x.Symbols()).ToArray();

    public static Symbol[] Symbols(this Block block) => block.Paragraphs.SelectMany(x => x.Symbols()).ToArray();

    public static Symbol[] Symbols(this Paragraph paragraph) => paragraph.Words.SelectMany(x => x.Symbols).ToArray();

    public static string[] Text(this TextAnnotation textAnnotation) => textAnnotation.Pages.SelectMany(x => x.Text()).ToArray();

    public static string[] Text(this Page page) => page.Blocks.SelectMany(x => x.Text()).ToArray();

    public static string[] Text(this Block block) => block.Paragraphs.Select(x => x.Text()).ToArray();

    public static string Text(this Paragraph paragraph) => string.Join(string.Empty, paragraph.Words.Select(x => x.Text()));

    public static string Text(this Word word) => string.Join(string.Empty, word.Symbols.SelectMany(x => x.Text));
}

【WPF】親からの比率でいろいろ指定したい、Data Binding, Prism, Reactive

ずーっと何年もこのブログのアクセスランキングTOP5は全部C#の記事なんですぉ。
ということでたまにはC#だぉ。


はい、表題の通り。例えば親の幅の半分の幅にしたいとかいうことあるよね。
Gridとかならさあれだけどさ。Canvas想定ね、Canvas
親のActualWidthを取ってこないとだめなケースの場合に使えるかと?VMでそんなもん持ちたくないやん?

はい、いつも通りおもむろに以下のクラスを作りましょう。OneWay前提なのでConvertBackは適当。

using System;
using System.Globalization;
using System.Windows.Data;

namespace Hoge.Converter
{
    public sealed class RatioConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            => (double)values[0] * (double)values[1];

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            => null;
    }
}

で、VM

using Prism.Mvvm;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;

namespace Hoge.Controls
{
    /// <summary>
    /// View model for HogeControl
    /// </summary>
    public sealed partial class HogeControlVM:  BindableBase
    {
        public ReadOnlyReactiveProperty<double> WidthRatioProperty { get; }
        private double Width { get => width; set => SetProperty(ref width, value); }
        private double width;
    }

    public partial class HogeControlVM
    {
        public HogeControlVM()
        {
            WidthRatioProperty = this.ObserveProperty(x => x.Width).ToReadOnlyReactiveProperty();
            Width = 0.5;
        }
    }
}



コードビハインドは省略で、View

<UserControl x:Class="Hoge.Controls.HogeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:c="clr-namespace:Hoge.Converter"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <c:RatioConverter x:Key="Ratio" />
    </UserControl.Resources>
    <Canvas Name="Canvas">
        <TextBox Text="Hogeeee">
            <TextBox.Width>
                <MultiBinding Converter="{StaticResource Ratio}">
                    <Binding ElementName="Canvas" Path="Width" />
                    <Binding Path="WidthRatioProperty.Value" />
                </MultiBinding>
            </TextBox.Width>
        </TextBox>
    </Canvas>
</UserControl>

IDE使わずに書いてるから動かなかったらごめんちゃい?


【2018年版】iOSアプリ新規で作る時にやること③

これ年に10回くらいはやるんだけど、どうにかならんのかなぁ。シェルスクリプト書けばいけそうだけど、Xcodeについてくのだるいし、労力と比較して迷う。

Carthage / RxSwift / Compass / XCGLogger / Reachability / Alamofire / R.swift / SwiftLint / Generamba / VIPER / fastlaneあたりが技術キーワードでしょうか。箇条書きでいきまっせ。
ぼくはもうCarthage対応してないやつは使わないので、Cocoapodsは出てきません。


③CD編です。fastlaneが主。
CIは対応してません。
あとたぶん設定抜けてたけど、AppIconは設定しておきましょう。
↑に挙げたやつ残はRxSwift / Compass / XCGLogger / Reachability / Alamofire / VIPERとまぁ実装寄りだし、このシリーズは今回で終わりかなぁ。

①はこちら
devdevdev.hatenablog.com

②はこちら
devdevdev.hatenablog.com

とりあえず

Xcode開いて、TARGETS→General→Signingで「Automatically manage signing」を外しておきましょう。

fastlane設定など

順番通りにやってくればfastlaneが使えるようになっているはずなのでインストールは省略。

bundle exec fastlane init

しましょう。あとは質問に答えていくだけ。
とりあえず2. Automate beta distribution to TestFlightやったあとに、3.Automate App Store distributionをやりました。Schemaが2つあるので2つともね。ただ、StagingのApp Store distributionはいらないので計3回。

・・・・・・という風にはうまくいきません。実際にはやっていません。


そして、Generambaとfastlaneの相性がよくない。ってゆーかGenerambaが見てるxcodeprojが古すぎ。本家Generamba更新なさすぎ。つらたん。自作?いや、めんどい。Fork?いや、誰かやってるっしょってわけで以下の方を発見。Xcode9.4で動いてるっぽいことをぼくが確認しました。これでもxcodeproj 1.5.9だからぎり。。fastlaneはxcodeproj 1.5.7以上。。fastlaneをグローバルにいれればいける気がするけど、でもグローバルにはいれたくない。

github.com

とゆーわけでGemfileを更新。

source "https://rubygems.org"
  
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'generamba', github: "Bassov/Generamba", branch: "develop"
gem "fastlane", "2.99.0"

bundle updateとかbundle installとかしましょう。



んで、本題。

とりあえず証明書管理用のgitリポジトリを作りましょう。

で、devices.txtを作成。
バイス追加するときはこいつを更新しましょう。

Device ID	Device Name
A5B5CD50-14AB-5AF7-8B78-AB4751AB10A8	NAME1
A5B5CD50-14AB-5AF7-8B78-AB4751AB10A7	NAME2

fastlane/Appfileとfastlane/Fastfileを作成。
Fastfileは手抜き。ちゃんと書きましょう。

Appfile

itc_team_id("123456789") # iTunes Connect Team ID
team_id("ABCDEFGHIJ") # Developer Portal Team ID

Fastfile

default_platform(:ios)

platform :ios do
  desc "Get the development certifacate files to Local Machine"
  lane :get_development_cert do
    match(type: "development",
          readonly: true,
          git_url: "証明書管理用のgitリポジトリURL",
          app_identifier: ["本番用Bundle Identifier", "ステージング用Bundle Identifier"])
  end

  desc "Push a new Staging beta build to TestFlight"
  lane :staging_beta do
    beta(
      app_identifier: "ステージング用Bundle Identifier",
      scheme: "Hoge_Staging"
    )
  end

  desc "Push a new Production beta build to TestFlight"
  lane :production_beta do
    beta(
      app_identifier: "本番用Bundle Identifier",
      scheme: "Hoge_Production"
    )
  end

  private_lane :beta do |options|
    # テスト
    # run_tests(scheme: options[:scheme])

    # デバイス更新
    register_devices(devices_file: "./devices.txt")

    # 証明書更新
    match(type: "appstore",
          force_for_new_devices: true,
          git_url: "証明書管理用のgitリポジトリURL",
          app_identifier: ["本番用Bundle Identifier", "ステージング用Bundle Identifier"])

    # ビルド番号更新
    increment_build_number

    # ビルド
    build_app(scheme: options[:scheme])

    # Testflightにアップロード
    upload_to_testflight(skip_waiting_for_build_processing: true,
                         app_identifier: options[:app_identifier])
  end

  private_lane :refresh_development_cert do
    match(type: "development",
          force: true,
          git_url: "証明書管理用のgitリポジトリURL",
          app_identifier: ["本番用Bundle Identifier", "ステージング用Bundle Identifier"])
  end
end



refresh_development_cert

bundle exec fastlane refresh_development_cert

開発用の証明書の更新をしまっせ。
なんかあんま頻繁に更新されたくない気がしたので、private。使うときは書き換え。運用でカバー)

get_development_cert

bundle exec fastlane get_development_cert

一般エンジニアが使う用。
初めてrefresh_development_certした時にパスワード設定するので、それを共有しといてあげましょう。

staging_beta

bundle exec fastlane staging_beta

ステージング用のアプリをTestflightへ
バイスとProvisioning Profileの更新(必要なら)もやってます。

production_beta

bundle exec fastlane production_beta

本番用のアプリをTestflightへ
バイスとProvisioning Profileの更新(必要なら)もやってます。

余談

ちなみにぼくはrswiftがArchive対象に入っちゃってたらしく最初エラーになったぉ。
最近のXcodeってほんと親切だよね。
何回もApple IDいれんのめんどくさいよね。user_nameっていう設定がたぶんAppfileにできるよ。



最後に

Xcode開いて、TARGETS→General→Signingで選べるやつ選んでおきましょう。

【Extension晒すシリーズ】UIView



import UIKit

extension UIView {

    public typealias Completion = () -> Void

    /// SubViewの一番右の座標取得
    public var maxSubViewRight: CGFloat {
        guard let view = subviews.max(by: { (a, b) in
            return a.frame.origin.x + a.frame.width <= b.frame.origin.x + b.frame.width
        }) else {
            return 0
        }
        return view.frame.origin.x + view.frame.width
    }

    /// SubViewの一番下の座標取得
    public var maxSubViewBottom: CGFloat {
        guard let view = subviews.max(by: { (a, b) -> Bool in
            return a.frame.origin.y + a.frame.height < b.frame.origin.y + b.frame.height
        }) else {
            return 0
        }
        return view.frame.origin.y + view.frame.height
    }

    /// SubViewの最大サイズ
    public var maxSubViewSize: CGSize {
        return CGSize(width: maxSubViewRight, height: maxSubViewBottom)
    }

    /// 角Radius
    @IBInspectable public var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
            layer.masksToBounds = newValue > 0
        }
    }

    /// 枠線太さ
    @IBInspectable public var borderWidth: CGFloat {
        get {
            return self.layer.borderWidth
        }
        set {
            self.layer.borderWidth = newValue
        }
    }

    /// 枠線色
    @IBInspectable public var borderColor: UIColor? {
        get {
            return UIColor.init(cgColor: self.layer.borderColor!)
        }
        set {
            self.layer.borderColor = newValue?.cgColor
        }
    }

    /// Nibを生成
    ///
    /// - Returns: Nib
    public class func createNib() -> UINib {
        return UINib(nibName: className, bundle: Bundle(for: self))
    }

    /// Nibからロード
    /// - returns: インスタンス
    open class func loadFromNib() -> UIView {
        return createNib().instantiate(withOwner: self, options: nil)[0] as! UIView
    }

    /// Marginを指定してaddSubview
    /// - parameter view:   view
    /// - parameter top:    top margin
    /// - parameter bottom: bottom margin
    /// - parameter left:   left margin
    /// - parameter right:  right margin
    public func addSubviewWithFit(_ view: UIView, top: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0, right: CGFloat = 0) {
        addSubview(view)
        view.fitToParent(top: top, bottom: bottom, left: left, right: right)
    }

    /// 親にfitさせる
    /// - parameter view:   view
    /// - parameter top:    top margin
    /// - parameter bottom: bottom margin
    /// - parameter left:   left margin
    /// - parameter right:  right margin
    public func fitToParent(top: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0, right: CGFloat = 0) {
        guard let superview = superview else {
            return
        }
        bounds = superview.bounds
        translatesAutoresizingMaskIntoConstraints = false

        superview.addConstraints([
            NSLayoutConstraint(
                item: self,
                attribute: .left,
                relatedBy: .equal,
                toItem: superview,
                attribute: .left,
                multiplier: 1.0,
                constant: left
            ),

            NSLayoutConstraint(
                item: self,
                attribute: .top,
                relatedBy: .equal,
                toItem: superview,
                attribute: .top,
                multiplier: 1.0,
                constant: top
            ),

            NSLayoutConstraint(
                item: self,
                attribute: .right,
                relatedBy: .equal,
                toItem: superview,
                attribute: .right,
                multiplier: 1.0,
                constant: right
            ),

            NSLayoutConstraint(
                item: self,
                attribute: .bottom,
                relatedBy: .equal,
                toItem: superview,
                attribute: .bottom,
                multiplier: 1.0,
                constant: bottom
            )
            ]
        )
    }

    /// centerにaddSubView
    ///
    /// - Parameter view: view
    public func addSubViewToCenter(_ view: UIView) {
        addSubview(view)
        view.moveToParentCenter()
    }

    /// 親のcenterに配置する
    public func moveToParentCenter() {
        guard let superview = superview else {
            return
        }
        bounds = superview.bounds
        translatesAutoresizingMaskIntoConstraints = false

        superview.addConstraints([
            NSLayoutConstraint(
                item: self,
                attribute: .centerX,
                relatedBy: .equal,
                toItem: superview,
                attribute: .centerX,
                multiplier: 1.0,
                constant: 1
            ),
            NSLayoutConstraint(
                item: self,
                attribute: .centerY,
                relatedBy: .equal,
                toItem: superview,
                attribute: .centerY,
                multiplier: 1.0,
                constant: 1
            )
            ]
        )
    }

    /// フェードイン
    ///
    /// - Parameters:
    ///   - duration: 時間
    ///   - completion: 完了通知
    public func fadeIn(duration: TimeInterval, completion: Completion?) {
        fadeImpl(true, duration: duration, completion: completion)
    }

    /// フェードアウト
    ///
    /// - Parameters:
    ///   - duration: 時間
    ///   - completion: 完了通知
    public func fadeOut(duration: TimeInterval, completion: Completion?) {
        fadeImpl(false, duration: duration, completion: completion)
    }

    private func fadeImpl(_ isfadeIn: Bool, duration: TimeInterval, completion: Completion?) {
        alpha = isfadeIn ? 0 : 1
        self.layer.removeAllAnimations()
        UIView.animate(withDuration: duration, animations: {
            self.alpha = isfadeIn ? 1 : 0
        }, completion: { finished in
            if finished {
                completion?()
            }
        })
    }

    /// 子を全て削除
    public func removeAllSubView() {
        subviews.forEach { (subView) in
            subView.removeFromSuperview()
        }
    }
}

【Swift】HTTPステータスコード列挙型 Powered by Wikipedia

devdevdev.hatenablog.com
↑これの更新版。4年ぶり。
Swiftで書き直しました。なんかQiitaで書いた気がするんだけどねー。akippaのやつかなー。



/// HTTPステータスコード列挙型
///
/// - cContinue: 継続 クライアントはリクエストを継続できる。
/// - switchingProtocols: プロトコル切り替え サーバはリクエストを理解し、遂行のためにプロトコルの切り替えを要求している
/// - processing: 処理中 WebDAVの拡張ステータスコード。処理が継続して行われていることを示す。
/// - ok: OK リクエストは成功し、レスポンスとともに要求に応じた情報が返される。
/// - created: 作成 リクエストは完了し、新たに作成されたリソースのURIが返される。
/// - accepted: 受理 リクエストは受理されたが、処理は完了していない。
/// - nonAuthoritativeInformation: 信頼できない情報 オリジナルのデータではなく、ローカルやプロキシ等からの情報であることを示す。
/// - noContent: 内容なし リクエストを受理したが、返すべきレスポンスエンティティが存在しない場合に返される。
/// - resetContent: 内容のリセット リクエストを受理し、ユーザエージェントの画面をリセットする場合に返される。
/// - partialContent: 部分的内容 部分的GETリクエストを受理したときに、返される。
/// - multiStatus: 複数のステータス WebDAVの拡張ステータスコード。
/// - IMUsed: IM使用 Delta encoding in HTTPの拡張ステータスコード。
/// - multipleChoices: 複数の選択 リクエストしたリソースが複数存在し、ユーザやユーザーエージェントに選択肢を提示するときに返される。
/// - movedPermanently: 恒久的に移動した リクエストしたリソースが恒久的に移動されているときに返される。Location:ヘッダに移動先のURLが示されている。
/// - found: 発見した リクエストしたリソースが一時的に移動されているときに返される。Location:ヘッダに移動先のURLが示されている。
/// - seeOther: 他を参照せよ リクエストに対するレスポンスが他のURLに存在するときに返される。Location:ヘッダに移動先のURLが示されている。
/// - notModified: 未更新 リクエストしたリソースは更新されていないことを示す。
/// - useProxy: プロキシを使用せよ レスポンスのLocation:ヘッダに示されるプロキシを使用してリクエストを行わなければならないことを示す。
/// - unUsed: 将来のために予約されている。ステータスコードは前のバージョンの仕様書では使われていたが、もはや使われておらず、将来のために予約されているとされる。
/// - temporaryRedirect: 一時的リダイレクト リクエストしたリソースは一時的に移動されているときに返される。Location:ヘッダに移動先のURLが示されている。
/// - badRequest: リクエストが不正である 定義されていないメソッドを使うなど、クライアントのリクエストがおかしい場合に返される。
/// - unauthorized: 認証が必要である Basic認証やDigest認証などを行うときに使用される。
/// - paymentRequired: 支払いが必要である 現在は実装されておらず、将来のために予約されているとされる。
/// - forbidden: 禁止されている リソースにアクセスすることを拒否された。
/// - notFound: 未検出 リソースが見つからなかった。
/// - methodNotAllowed: 許可されていないメソッド 許可されていないメソッドを使用しようとした。
/// - notAcceptable: 受理できない Accept関連のヘッダに受理できない内容が含まれている場合に返される。
/// - proxyAuthenticationRequired: プロキシ認証が必要である プロキシの認証が必要な場合に返される。
/// - requestTimeout: リクエストタイムアウト リクエストが時間以内に完了していない場合に返される。
/// - conflict: 矛盾 要求は現在のリソースと矛盾するので完了できない。
/// - gone: 消滅した。ファイルは恒久的に移動した。
/// - lengthRequired: 長さが必要 Content-Lengthヘッダがないのでサーバーがアクセスを拒否した場合に返される。
/// - preconditionFailed: 前提条件で失敗した 前提条件が偽だった場合に返される。
/// - requestEntityTooLarge: リクエストエンティティが大きすぎる リクエストエンティティがサーバの許容範囲を超えている場合に返す。
/// - requestURITooLong: リクエストURIが大きすぎる URIが長過ぎるのでサーバが処理を拒否した場合に返す。
/// - unsupportedMediaType: サポートしていないメディアタイプ 指定されたメディアタイプがサーバでサポートされていない場合に返す。
/// - requestedRangeNotSatisfiable: リクエストしたレンジは範囲外にある 実ファイルのサイズを超えるデータを要求した。
/// - expectationFailed: Expectヘッダによる拡張が失敗 その拡張はレスポンスできない。またはプロキシサーバは、次に到達するサーバがレスポンスできないと判断している。
/// - imaTeapot: 私はティーポット HTCPCP/1.0の拡張ステータスコード。
/// - unprocessableEntity: 処理できないエンティティ WebDAVの拡張ステータスコード。
/// - locked: ロックされている WebDAVの拡張ステータスコード。リクエストしたリソースがロックされている場合に返す。
/// - failedDependency: 依存関係で失敗 WebDAVの拡張ステータスコード。
/// - upgradeRequired: アップグレード要求 Upgrading to TLS Within HTTP/1.1の拡張ステータスコード。
/// - internalServerError: サーバ内部エラー サーバ内部にエラーが発生した場合に返される。
/// - notImplemented: 実装されていない 実装されていないメソッドを使用した。
/// - badGateway: 不正なゲートウェイ ゲートウェイ・プロキシサーバは不正な要求を受け取り、これを拒否した。
/// - serviceUnavailable: サービス利用不可 サービスが一時的に過負荷やメンテナンスで使用不可能である。
/// - gatewayTimeout: ゲートウェイタイムアウト ゲートウェイ・プロキシサーバはURIから推測されるサーバからの適切なレスポンスがなくタイムアウトした。
/// - httpVersionNotSupported: サポートしていないHTTPバージョン リクエストがサポートされていないHTTPバージョンである場合に返される。
/// - variantAlsoNegotiates: Transparent Content Negotiation in HTTPで定義されている拡張ステータスコード。
/// - insufficientStorage: 容量不足 WebDAVの拡張ステータスコード。リクエストを処理するために必要なストレージの容量が足りない場合に返される。
/// - bandwidthLimitExceeded: 帯域幅制限超過 そのサーバに設定されている帯域幅(転送量)を使い切った場合に返される。
/// - notExtended: 拡張できない An HTTP Extension Frameworkで定義されている拡張ステータスコード。
public enum HttpStatusCode: Int {

    case cContinue = 100

    case switchingProtocols = 101

    case processing = 102

    case ok = 200

    case created = 201

    case accepted = 202

    case nonAuthoritativeInformation = 203

    case noContent = 204

    case resetContent = 205

    case partialContent = 206

    case multiStatus = 207

    case IMUsed = 226

    case multipleChoices = 300

    case movedPermanently = 301

    case found = 302

    case seeOther = 303

    case notModified = 304

    case useProxy = 305

    case unUsed = 306

    case temporaryRedirect = 307

    case badRequest = 400

    case unauthorized = 401

    case paymentRequired = 402

    case forbidden = 403

    case notFound = 404

    case methodNotAllowed = 405

    case notAcceptable = 406

    case proxyAuthenticationRequired = 407

    case requestTimeout = 408

    case conflict = 409

    case gone = 410

    case lengthRequired = 411

    case preconditionFailed = 412

    case requestEntityTooLarge = 413

    case requestURITooLong = 414

    case unsupportedMediaType = 415

    case requestedRangeNotSatisfiable = 416

    case expectationFailed = 417

    case imaTeapot = 418

    case unprocessableEntity = 422

    case locked = 423

    case failedDependency = 424

    case upgradeRequired = 426

    case internalServerError = 500

    case notImplemented = 501

    case badGateway = 502

    case serviceUnavailable = 503

    case gatewayTimeout = 504

    case httpVersionNotSupported = 505

    case variantAlsoNegotiates = 506

    case insufficientStorage = 507

    case bandwidthLimitExceeded = 509

    case notExtended = 510
}


【iOS】プラットフォーム情報取得4~Swift編~【2018年版】

devdevdev.hatenablog.com
↑3年半ぶりに更新。Swiftで書き直したのとiPhoneX / iPad Proまで入ってるのと、ちょこっと機能追加!



import UIKit

public struct Device {

    static let iPhoneX = "iPhone X"
    static let iPad = "iPad"
    static let iPodTouch = "iPod Touch"
    static let iPhone = "iPhone"

    static var deviceInfo: String {
        var size: Int = 0
        sysctlbyname("hw.machine", nil, &size, nil, 0)
        var machine = [CChar](repeating: 0, count: Int(size))
        sysctlbyname("hw.machine", &machine, &size, nil, 0)
        let code: String = String(cString: machine)

        let deviceCodeDic: [String: String] = [
            /* Simulator */
            "i386": "Simulator",
            "x86_64": "Simulator",
            /* iPod */
            "iPod1,1": "iPod Touch 1th",            // iPod Touch 1th Generation
            "iPod2,1": "iPod Touch 2th",            // iPod Touch 2th Generation
            "iPod3,1": "iPod Touch 3th",            // iPod Touch 3th Generation
            "iPod4,1": "iPod Touch 4th",            // iPod Touch 4th Generation
            "iPod5,1": "iPod Touch 5th",            // iPod Touch 5th Generation
            "iPod7,1": "iPod Touch 6th",            // iPod Touch 6th Generation
            /* iPhone */
            "iPhone1,1": "iPhone 2G",                 // iPhone 2G
            "iPhone1,2": "iPhone 3G",                 // iPhone 3G
            "iPhone2,1": "iPhone 3GS",                // iPhone 3GS
            "iPhone3,1": "iPhone 4",                  // iPhone 4 GSM
            "iPhone3,2": "iPhone 4",                  // iPhone 4 GSM 2012
            "iPhone3,3": "iPhone 4",                  // iPhone 4 CDMA For Verizon,Sprint
            "iPhone4,1": "iPhone 4S",                 // iPhone 4S
            "iPhone5,1": "iPhone 5",                  // iPhone 5 GSM
            "iPhone5,2": "iPhone 5",                  // iPhone 5 Global
            "iPhone5,3": "iPhone 5c",                 // iPhone 5c GSM
            "iPhone5,4": "iPhone 5c",                 // iPhone 5c Global
            "iPhone6,1": "iPhone 5s",                 // iPhone 5s GSM
            "iPhone6,2": "iPhone 5s",                 // iPhone 5s Global
            "iPhone7,1": "iPhone 6 Plus",             // iPhone 6 Plus
            "iPhone7,2": "iPhone 6",                  // iPhone 6
            "iPhone8,1": "iPhone 6S",                 // iPhone 6S
            "iPhone8,2": "iPhone 6S Plus",            // iPhone 6S Plus
            "iPhone8,4": "iPhone SE", // iPhone SE
            "iPhone9,1": "iPhone 7",                  // iPhone 7 A1660,A1779,A1780
            "iPhone9,3": "iPhone 7",                  // iPhone 7 A1778
            "iPhone9,2": "iPhone 7 Plus",             // iPhone 7 Plus A1661,A1785,A1786
            "iPhone9,4": "iPhone 7 Plus",             // iPhone 7 Plus A1784
            "iPhone10,1": "iPhone 8",                  // iPhone 8 A1863,A1906,A1907
            "iPhone10,4": "iPhone 8",                  // iPhone 8 A1905
            "iPhone10,2": "iPhone 8 Plus",             // iPhone 8 Plus A1864,A1898,A1899
            "iPhone10,5": "iPhone 8 Plus",             // iPhone 8 Plus A1897
            "iPhone10,3": "iPhone X",                  // iPhone X A1865,A1902
            "iPhone10,6": "iPhone X",                  // iPhone X A1901

            /* iPad */
            "iPad1,1": "iPad 1 ",                   // iPad 1
            "iPad2,1": "iPad 2 WiFi",               // iPad 2
            "iPad2,2": "iPad 2 Cell",               // iPad 2 GSM
            "iPad2,3": "iPad 2 Cell",               // iPad 2 CDMA (Cellular)
            "iPad2,4": "iPad 2 WiFi",               // iPad 2 Mid2012
            "iPad2,5": "iPad Mini WiFi",            // iPad Air WiFi
            "iPad2,6": "iPad Mini Cell",            // iPad Mini GSM (Cellular)
            "iPad2,7": "iPad Mini Cell",            // iPad Mini Global (Cellular)
            "iPad3,1": "iPad 3 WiFi",               // iPad 3 WiFi
            "iPad3,2": "iPad 3 Cell",               // iPad 3 CDMA (Cellular)
            "iPad3,3": "iPad 3 Cell",               // iPad 3 GSM (Cellular)
            "iPad3,4": "iPad 4 WiFi",               // iPad 4 WiFi
            "iPad3,5": "iPad 4 Cell",               // iPad 4 GSM (Cellular)
            "iPad3,6": "iPad 4 Cell",               // iPad 4 Global (Cellular)
            "iPad4,1": "iPad Air WiFi",             // iPad Air WiFi
            "iPad4,2": "iPad Air Cell",             // iPad Air Cellular
            "iPad4,4": "iPad Mini 2 WiFi",          // iPad mini 2 WiFi
            "iPad4,5": "iPad Mini 2 Cell",          // iPad mini 2 Cellular
            "iPad4,6": "iPad Mini 2 China",         // iPad mini 2 ChinaModel
            "iPad4,7": "iPad Mini 3 WiFi",          // iPad mini 3 WiFi
            "iPad4,8": "iPad Mini 3 Cell",          // iPad mini 3 Cellular
            "iPad4,9": "iPad Mini 3 China",         // iPad mini 3 ChinaModel
            "iPad5,1": "iPad Mini 4 WiFi",          // iPad Mini 4 WiFi
            "iPad5,2": "iPad Mini 4 Cell",          // iPad Mini 4 Cellular
            "iPad5,3": "iPad Air 2 WiFi",           // iPad Air 2 WiFi
            "iPad5,4": "iPad Air 2 Cell",           // iPad Air 2 Cellular
            "iPad6,3": "iPad Pro 9.7inch WiFi",     // iPad Pro 9.7inch WiFi
            "iPad6,4": "iPad Pro 9.7inch Cell",     // iPad Pro 9.7inch Cellular
            "iPad6,7": "iPad Pro 12.9inch WiFi",    // iPad Pro 12.9inch WiFi
            "iPad6,8": "iPad Pro 12.9inch Cell"]    // iPad Pro 12.9inch Cellular

        if let deviceName = deviceCodeDic[code] {
            return deviceName
        } else {
            if code.range(of: "iPod") != nil {
                return iPodTouch
            } else if code.range(of: iPad) != nil {
                return iPad
            } else if code.range(of: iPhone) != nil {
                return iPhone
            } else {
                return "unknownDevice"
            }
        }
    }

    static let baseScreenWidth: CGFloat = 320.0

    static var bounds: CGRect {
        return UIScreen.main.bounds
    }

    static var screenWidth: CGFloat {
        return UIScreen.main.bounds.size.width
    }

    static var screenHeight: CGFloat {
        return UIScreen.main.bounds.size.height
    }

    static var statusBarHeight: CGFloat {
        return UIApplication.shared.statusBarFrame.height
    }

    static var screenRatio: CGFloat {
        return UIScreen.main.bounds.size.width / baseScreenWidth
    }

    static func navBarHeight(navigationController: UINavigationController) -> CGFloat {
        return navigationController.navigationBar.frame.size.height
    }

    static func commonTopHeight(navigationController: UINavigationController) -> CGFloat {
        return navBarHeight(navigationController: navigationController) + statusBarHeight
    }
}


【2018年版】iOSアプリ新規で作る時にやること②

これ年に10回くらいはやるんだけど、どうにかならんのかなぁ。シェルスクリプト書けばいけそうだけど、Xcodeについてくのだるいし、労力と比較して迷う。

Carthage / RxSwift / Compass / XCGLogger / Reachability / Alamofire / R.swift / SwiftLint / Generamba / VIPER / fastlaneあたりが技術キーワードでしょうか。箇条書きでいきまっせ。
ぼくはもうCarthage対応してないやつは使わないので、Cocoapodsは出てきません。

②Generamba編です。

①はこちら
devdevdev.hatenablog.com




Gem

Gemfileを用意

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "generamba"
# Xcode9.3以降はこう
gem 'generamba', github: "surfstudio/Generamba", branch: "develop"

gem "fastlane"
bundle install --path vendor/bundler

Generamba

セットアップ

bundle exec generamba setup

で、質問に答えましょう。

秘蔵のtemplateをプロジェクトに投入。以下をベースにカスタムしてます。
https://github.com/rambler-digital-solutions/Generamba

作るときは以下

bundle exec generamba gen Hoge template_name