【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をグローバルにいれればいける気がするけど、でもグローバルにはいれたくない。
とゆーわけで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