【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
【2018年版】iOSアプリ新規で作る時にやること①
これ年に10回くらいはやるんだけど、どうにかならんのかなぁ。シェルスクリプト書けばいけそうだけど、Xcodeについてくのだるいし、労力と比較して迷う。
Carthage / RxSwift / Compass / XCGLogger / Reachability / Alamofire / R.swift / SwiftLint / Generamba / VIPER / fastlaneあたりが技術キーワードでしょうか。箇条書きでいきまっせ。
ぼくはもうCarthage対応してないやつは使わないので、Cocoapodsは出てきません。
①プロジェクト作成~設定・各種ライブラリ導入編です。
Apple Developerアカウントもらう
誰かにもらいましょう
App ID決める
誰かに決めてもらいましょう
PROJECT作る
作りましょう。
.gitignore投入
### https://raw.github.com/github/gitignore/160d27e2bebf784c4f4a1e070df057f3868b62bc/Objective-C.gitignore # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xcuserstate ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ *.generated.swift vendor/bundler
PROJECT→Info
iOS Deployment Target
誰かに決めてもらいましょう
Configurations
Debug→ProductionDebug
Release→ProductionRelease
に変更して
それぞれをDuplicateして
StagingDebugとStagingReleaseを作る。
Developが必要ならDevelopDebugとDevelopReleaseも。
でもDevelopはあんま作りたくない。なんでiOSエンジニアがローカルにバックエンドの開発環境作らなきゃいけないのかうんぬんかんぬん。
Productionも正直用意するか迷う。何人かで開発するときはいらないかも。
Schema
デフォルトでできてるやつにsuffix:_Productionをつける
Duplicateして_Stagingを作る
両方Sharedにする
_Stagingのほうを編集。Build ConfigurationをStagingDebugとStagingReleaseに変更。
PROJECT→Build Settings
Versioning
Current Project Version:1
Versioning System:Apple Generic
に変更。
Swift Compiler - Custom Flags→Other Swift Flags
ProductionDebug:-D DEBUG
StagingDebug: -D DEBUG -D STAGING
追加。
TARGETS→General
Identity→Version
Signing
Deployment Info
をいじる
Deployment Info→Main Interfaceはクリアして、Main.storyboardを削除。
TARGETS→Capabilities
必要なのをオン
TARGETS→Info
Bundle Name:$(BundleNamePrefix)アプリ名
TARGETS→Build Settings
User-DefinedにBundleNamePrefixとConfigurationNameを追加。Stagingだけ設定をお好みで追加。
Packaging→Product Bundle Identifier:[お好みで]$(ConfigurationName)
SwiftLint導入
インストール。グローバルにいれるのすきじゃないんだけど、Build Phaseで使いたいのでまぁしゃーない。もう、homebrewのインストールはいいっしょ。。このブログ内検索すればあるはず。
brew install swiftlint
TARGETS→Build PhaseでNew Run Script Phase
#!/bin/sh if which swiftlint >/dev/null; then swiftlint autocorrect swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi
で、プロジェクトルートに.swiftlint.ymlを用意。
included: - [Project Module Nameを] excluded: - Pods/ - Carthage/ - fastlane - [Project Module Nameを]/Application/R.generated.swift disabled_rules: - force_cast - force_try - force_unwrapping file_length: warning: 1000 error: 1500 type_body_length: warning: 500 error: 750 function_body_length: warning: 200 error: 300 line_length: warning: 200 error: 300 variable_name: min_length: 1
で、ビルドエラーになったら修正。
Carthage導入
インストール。
brew install carthage
プロジェクトルートにCartfileを作る。基本は以下。
バージョンは2018/06現在の最新。
# General github "ReactiveX/RxSwift" "4.2.0" github "hyperoslo/Compass" "6.0.0" # Resources github "mac-cain13/R.swift.Library" "v4.0.0" # Debug github "DaveWoodCom/XCGLogger" "6.0.4" # API github "ashleymills/Reachability.swift" "v4.1.0" github "Alamofire/Alamofire" "4.7.2"
作れたら
carthage update --cache-builds --platform iOS
Cartfileを変更したら毎回これやりましょう。初回はものすごい時間がかかりまっせ。
TARGETS→General→Linked Frameworks and Librariesで+→Add Other...で
Carthage/Build/iOSの下にできた.frameworkたちを追加。
TARGETS→Build PhaseでNew Run Script Phase
/usr/local/bin/carthage copy-frameworks
Input Filesにさっき入れたFrameworkを全て追加。。。
・7/1追記
github.com
公式の説明の通り、ドラッグ&ドロップだとなんかうまくいかなかった。
たぶんOutput Filesもちゃんと書いたほうがよい気がする
R.swift導入
github.com
ここから任意のバージョンのzipをダウンロードして解凍。
rswiftをプロジェクト内のお好みの場所に。
TARGETS→Build PhaseでNew Run Script Phase
これはComple Sourcesより前にやったほうがよい(上から順番に実行されるから上のほうにおくとよい)と思う。
"$PROJECT_DIR/rswiftのパス" generate "$PROJECT_DIR/R.generated.swiftを置きたいディレクトリのパス(.swiftlint.yml参照)"
ビルドするとR.generated.swiftができるので、プロジェクトに追加しておきましょう。
【Android】SimpleRecyclerViewAdapter
めずらしくあんどろいだーーーーーー
ねいてぃぶはらくちんでよいね!!!最高!ねむい!体調悪い!つらい!でも納期も時間も待ってくれない!
はい、RecyclerViewAdapterね。簡単なやつならこうしましょう。
app/build.gradle
抜粋です。databindingも使うのでね。
apply plugin: 'kotlin-kapt' android { dataBinding { enabled = true } } dependencies { ext.android_support_version = "27.1.1" implementation "com.android.support:recyclerview-v7:$android_support_version" }
SimpleRecyclerViewAdapter.kt
おもむろに以下をじっそうしましょう。
package hoge; import android.content.Context import android.support.v7.widget.RecyclerView import android.view.View import android.view.ViewGroup import java.lang.reflect.ParameterizedType abstract class SimpleRecyclerViewAdapter<V : View, I : Any?>(defaultItems: ArrayList<I>? = null) : RecyclerView.Adapter<SimpleRecyclerViewAdapter<V, I>.ItemViewHolder>() { protected val isEmpty get() = !items.any() private val items: ArrayList<I> = arrayListOf() init { defaultItems?.let { items.addAll(it) } } fun removeAll() { items.clear() notifyDataSetChanged() } fun addItem(item: I) { items.add(item) notifyItemInserted(items.count()) } fun removeItem(item: I) { val index = items.indexOf(item) items.removeAt(index) notifyItemRemoved(index) } fun updateItem(position: Int, item: I) { items[position] = item notifyItemChanged(position) } final override fun getItemCount() = items.count() final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(onCreateItemView(parent.context).apply { layoutParams = RecyclerView.LayoutParams( RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT ) }) @Suppress("UNCHECKED_CAST") final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { (holder.itemView as? V)?.let { onBindItemView(it, items[position], position) } } @Suppress("UNCHECKED_CAST") protected open fun onCreateItemView(context: Context) = ((javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>) .constructors.first().newInstance(context) as V protected abstract fun onBindItemView(itemView: V, item: I, position: Int) inner class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) }
使い方
view_hoge_list_item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/textViewTitle" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
HogeListItemView.kt
package hoge import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.widget.LinearLayout import hoge.R import kotlinx.android.synthetic.main.view_hoge_list_item.view.* internal class HogeListItemView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) { init { LayoutInflater.from(context).inflate(R.layout.view_hoge_list_item, this) } fun setTitle(title: String) { textViewTitel.text = title } }
HogeFragment.kt
省略。Bindingのサンプル書くのめんどい。
HogeFragmentVM.kt
package hoge import android.content.Context import hoge.SimpleRecyclerViewAdapter import hoge.HogeListItemView internal class HogeFragmentVM() { val recyclerViewAdapter = object : SimpleRecyclerViewAdapter<HogeListItemView, String>(arrayListOf<String>("Hogeeeeee", "Aaaaaaaaaaa")) { override fun onBindItemView(itemView: HogeListItemView, item: String, position: Int) { itemView.setTitle(item) } } }
fragment_hoge.xml
抜粋。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="vm" type="hoge.HogeFragmentVM" /> </data> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="android.support.v7.widget.LinearLayoutManager" app:recyclerViewAdapter="@{vm.recyclerViewAdapter}" /> </layout>
onBindItemViewだけoverrideすればいいから簡単ですね。はい。いじょー
Prism PubSubEventを簡単に
GetEventとか長ったらしい。
おもむろに以下のクラスを実装しましょう。
using Prism.Events; using System; namespace Hoge { public abstract class StaticPubSubEvent<T> : PubSubEvent where T : StaticPubSubEvent<T>, new() { private static EventAggregator eventAggregator = new EventAggregator(); private static readonly T instance = eventAggregator.GetEvent<T>(); protected StaticPubSubEvent() { } public static new void Publish() => eventAggregator.GetEvent<T>().InternalPublish(); public static new SubscriptionToken Subscribe(Action action) => Subscribe(action, ThreadOption.PublisherThread); public static new SubscriptionToken Subscribe(Action action, ThreadOption threadOption) => Subscribe(action, threadOption, false); public static new SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive) => Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive); public static new SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive) { var actionReference = new DelegateReference(action, keepSubscriberReferenceAlive); EventSubscription subscription; switch (threadOption) { case ThreadOption.PublisherThread: subscription = new EventSubscription(actionReference); break; case ThreadOption.BackgroundThread: subscription = new BackgroundEventSubscription(actionReference); break; case ThreadOption.UIThread: if (instance.SynchronizationContext == null) throw new InvalidOperationException(); subscription = new DispatcherEventSubscription(actionReference, instance.SynchronizationContext); break; default: subscription = new EventSubscription(actionReference); break; } return eventAggregator.GetEvent<T>().InternalSubscribe(subscription); } } }
で、イベントクラスを用意
namespace Hoge { public sealed class HogeEvent : StaticPubSubEvent<HogeEvent> { } }
Subscriber
HogeEvent.Subscribe(() => { }, ThreadOption.UIThread);
Publisher
HogeEvent.Publish();
ぼくは気にしないけど、若干いまいちですね。何がいまいちかわかる人だけ自己責任で使ってくださいw(ヒント:EventAggregator