2016振り返り
2016も残りわずかとなったところで急いで今年を振り返ります!
仕事
1月にLIMIAのiPhoneアプリをリリースしました。 フルSwiftで開発しました。
2月にはAppStoreの新着ベストアプリにも選ばれました!
3年半ほどお世話になったグリーを6月に退社してSupershipにジョインしました。
開発の途中から参加してものすごい勢いで実装して7月にはSunnychatのiPhoneアプリをリリースしました。 こちらもSwiftです。
プライベート
こどもが産まれまして、ワークライフバランスがこれまでと著しく変わってきました。
今はとにかく自分の時間がとれなくて勉強するのがなかなかたいへんです。
勉強
Elixirを勉強しましたけどコーディングは全然です
ランニング
10月まではほぼ毎週走ってたんですが、こどもが産まれてそれどころではなくなったので最近は全く走れてないです。。
まとめ
2016は仕事、プライベートともに激動の年だったなぁと。
2017は子育てを頑張りつつ、自分の時間もうまくつくってなにかやっていきたいですね
Swiftでの複数にデリゲート multicast delegate
Swiftにはweak reference arrayがないので、NSHashTableを利用します
protocol SampleDelegate : class { func sampleDelegateDidFinish() }
class SampleClass { let delegates = NSHashTable<AnyObject>() // AnyObjectをSampleDelegateにするとSwift3時点ではコンパイルに通らない
delegateを呼ぶとき
func someFunc() { ... delegates.objectEnumerator().enumerated() .map { $0.element as? SampleDelegate } .forEach { $0?.sampleDelegateDidFinish()) } ... }
popToRootViewControllerで画面を消すとviewWillDisappearでnavigationControllerがnilになる
現象が伝えにくいので、図に表すと以下のような感じで、 TabbarController内にNavigationControllerをもたせた状態で、いくつかViewControllerをPushViewControllerします。 そして、2つ以上PushViewControllerした状態で一番上のViewControllerのナビバーを透明にした場合にちょっと問題が発生します。
一番上のViewControllerでは以下のようにして、ナビバーを透明にして、消すときにナビバーを元に戻す用にしています。
class SampleViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() navigationController?.navigationBar.isTranslucent = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil navigationController?.navigationBar.isTranslucent = false } }
普通に戻るボタンを押した場合はこれで正常に動作するのですが、 タブバーのボタンをタップしてrootviewcontrollerに戻った場合に、viewWillDisappearが呼ばれたときにはnavigationControllerがnilになっているため、ナビバーが透明なままになってしまうという問題が発生しました。。
対策
その1
やっつけ仕事感のある対応としてはnavigationControllerを自力で保持するというやりかたで、、
private weak var toKeepNavigationController: UINavigationController? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) toKeepNavigationController = navigationController navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() navigationController?.navigationBar.isTranslucent = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) toKeepNavigationController?.navigationBar.setBackgroundImage(nil, for: .default) toKeepNavigationController?.navigationBar.shadowImage = nil toKeepNavigationController?.navigationBar.isTranslucent = false }
その2
willMoveToParentViewControllerの時点ではnavigationControllerはnilになっていないようだったので、ViewControllerが消えるタイミング(parentがnilになったとき)にナビバーをもとにもどします
override func willMove(toParentViewController parent: UIViewController?) { super.willMove(toParentViewController: parent) if parent == nil { navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil navigationController?.navigationBar.isTranslucent = false } }
たぶん2のほうがいいと思います!
おかしいぞとかあればツッコミお願いします!
Swift3.0.1で若干変わったIntなどの数値型⇔AnyObjectのcast
Xcode8(Swift3.0.0)だと↓のテストは通るんですが、
import XCTest @testable import TypeTest class TypeTests: XCTestCase { func testType_WhenIntToAnyObject() { let intValue = ["hoge": Int(1) as AnyObject] XCTAssertTrue(intValue["hoge"] is Int) XCTAssertTrue(intValue["hoge"] is Float) XCTAssertTrue(intValue["hoge"] is Double) XCTAssertTrue(intValue["hoge"] is CGFloat) } func testType_WhenCGFloatToAnyObject() { let cgfloatValue = ["hoge": CGFloat(1.0) as AnyObject] XCTAssertTrue(cgfloatValue["hoge"] is Float) XCTAssertTrue(cgfloatalue["hoge"] is Double) XCTAssertTrue(cgfloatValue["hoge"] is CGFloat) } }
Xcode8.1(Swift3.0.1)だと↓のようにAnyObjectにcastする前の型のみ正しく判定されるようになってました
SE-0139によるところかと思われます。 あんまりちゃんと読んでないですが、以下のようなこと書いてあったし。
Bridged NSNumber and NSValue objects must be castable back to their original Swift value types.
雑にAnyObjectをcast backしてる場合はSwfit3.0.1にするとcastに失敗するようになるので注意が必要かもです
CGRectの新旧書き方対応表
古 | 新 |
---|---|
CGRectGetWidth( rect ) | rect.width |
CGRectGetHeight( rect ) | rect.height |
CGRectGetMinX( rect ) | rect.minX |
CGRectGetMidX( rect ) | rect.midX |
CGRectGetMaxX( rect ) | rect.maxX |
CGRectGetMinY( rect ) | rect.minY |
CGRectGetMidY( rect ) | rect.midY |
CGRectGetMaxY( rect ) | rect.maxY |
CGRectIsNull( rect ) | rect.isNull |
CGRectIsEmpty( rect ) | rect.isEmpty |
CGRectIsInfinite( rect ) | rect.isInfinite |
CGRectStandardize( rect ) | rect.standardize |
CGRectIntegral( rect ) | rect.integral |
CGRectInset(rect, 1.0, -2.0) | rect.insetBy(dx: 1.0, dy: -2.0) |
CGRectOffset(rect, -1.0, 2.0) | rect.offsetBy(dx: -1.0, dy: 2.0) |
CGRectUnion(rect1, rect2) | rect1.union(rect2) |
CGRectIntersection( rect1 ,rect2) | rect1.intersect(rect2) |
CGRectContainsRect( rect1,rect2) | rect1.contains(rect2) |
CGRectContainsPoint(rect ,point) | rect.contains(point) |
CGRectIntersectsRect( rect1,rect2 ) | rect1.intersects(rect2) |
Xcode8にしたときのプッシュ通知対応
Project SettingsのCapabilitiesのタブを開き、PushNotificationsをONにします
production.entitlements
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>aps-environment</key> <string>production</string> </dict> </plist>
development.entitlements
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>aps-environment</key> <string>development</string> <key>keychain-access-groups</key> <array> <string>$(AppIdentifierPrefix)share</string> </array> </dict> </plist>
Build SettingsのCode Signing Entitlementsで、Configurationによって使うentitlementsを変えるようにします
最後にこれは必須ではないですが、
if #available(iOS 10.0, *) { UNUserNotificationCenter.currentNotificationCenter().requestAuthorizationWithOptions([.Alert, .Sound, .Badge]) { granted, _ in if granted { UIApplication.sharedApplication().registerForRemoteNotifications() } } } else { let settings = UIUserNotificationSettings(forTypes: [.Alert, .Sound, .Badge], categories: nil) UIApplication.sharedApplication().registerUserNotificationSettings(settings) UIApplication.sharedApplication().registerForRemoteNotifications() }
Xcode8のxcodebuildでarchive時にsignining周りでエラーがでたときの対処法
Xcode8でxcodebuildを使ってコマンドラインでarchiveしようとすると以下のようなエラーがでて失敗するようになりました。
Check dependencies XXX has conflicting provisioning settings.XXX is automatically signed, but provisioning profile XXX_Adhoc has been manually specified. Set the provisioning profile value to "Automatic" in the build settings editor, or switch to manual signing in the project editor. Code signing is required for product type 'Application' in SDK 'iOS 10.0'
使っていたコマンドは以下のような感じで、通常の設定では実機で試しやすいようにCode Signing IdentityをiOS Developerにしていて、 コマンドラインで実行するときにDistributionに変更するようにしていました。
xcodebuild \ -workspace XXX \ -scheme XXX \ -configuration XXX \ -archivePath XXX \ CODE_SIGN_IDENTITY="iPhone Distribution: XXX (XXXX)" \ archive
がXcode8ではこれでは通りません
対処法
Xcode8からは自動でsigningを管理する機能がついたため、これをxcodebuildで変更しようとするとコンフリクトしてると怒られるみたいです、エラーを見る限り。。
自分のケースだとコマンドラインでのビルド時にCode Signingを変更したいので、自動管理を諦めます。
よって、まずは、Project SettingのGeneralタブのSigningにある"Automatically manage signingをOFFにします
これだけでは解決せず、Xcode8から追加されたPROVISIONING_PROFILE_SPECIFIERを設定してやると解決しました。 PROVISIONING_PROFILE_SPECIFIERにはprovisioning profileの名前をいれればOKです。
xcodebuild \ -workspace XXX \ -scheme XXX \ -configuration XXX \ -archivePath XXX \ CODE_SIGN_IDENTITY="iPhone Distribution: XXX (XXXX)" \ PROVISIONING_PROFILE_SPECIFIER=XXX\ archive
Xcode8でテストが実行できない Could not determine bundle identifier for TEST_HOST
Xcode8でxcodebuildでtestを走らせると以下のようなエラーがでてテストが実行できなくなりました
xcodebuild: error: Failed to build workspace XXX with scheme XXX. Reason: Could not determine bundle identifier for XXXTests's TEST_HOST:
どうもTEST_HOST、つまりテスト対象のビルドバイナリがないと怒られてるみたいで、 testの前にbuildを追加したら通りました。
$ xcodebuild -workspace XXX -scheme XXX -configuration Debug clean build test -destination 'platform=iOS Simulator,OS=10.0,name=iPhone 6'
ちなみに、xcodebuildじゃなくて、Xcodeでテスト実行する際も、ビルドする前にいきなりテストを実行すると同じエラーが発生します。 これについては、先にビルドしておけば解決します。
動画のフォトアルバムへの保存
動画のフォトアルバムへの保存
iOS8まで
import AssetsLibrary ... ALAssetsLibrary().writeVideoAtPathToSavedPhotosAlbum(filePathURL) { _ in // 完了後の処理 }
iOS9以降
import Photos ... PHPhotoLibrary.sharedPhotoLibrary().performChanges( { PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(filePathURL) }, completionHandler: { _ in // 完了後の処理 } )
Android Studioをインストールしてプロジェクトをビルドするときに躓いたところ
Androidの既存のプロジェクトをビルドしようとしてAndroid Studioのインストールからはじめると 毎回同じようなところで躓いている気がするのでメモ
エラーで Unsupported major.minor version 52.0 が出た時
原因
これはAndroidStudioが使っているJDKが古いと出る
対策
- まずはJDK8をインストールする
- AndroidStudioを起動して、File -> Other Settings -> Default Project Structure を選択
- JDK locationを変更してJDK8のPathに変更する
エラーで Error:failed to find Build Tools revision 23.0.0 rc3 が出た時
原因
これは対応するAndroid SDK Build Toolsがインストールされていないのが原因
対策
既存のプロジェクトでSwift2.3のままXcode8でビルドを通す
Xcode8でビルドするとデフォルトではSwift3でコンパイルされてしまいます。 とりあえずXcode8にあげるけどSwift2.3のままにしておいて、Swift3対応は後でという場合の対処方法です。
Build Settings
Build Settingsの"Use Legacy Swift Language Version"を"Yes"にする
Cocoapodsの変更
Podfileの一番最後に以下を追加
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '2.3' end end end
ライブラリをXcode8&Swift2.3対応のブランチを指定するようにする
Swift2.3とSwift3ではバイナリ互換がないので、ライブラリも含めてSwift2.3でコンパイルする必要があります
例
pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire', :branch => 'swift2.3'
pod 'Realm', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'master', :submodules => true pod 'RealmSwift', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'master', :submodules => true
AnsibleでUbuntu16.04使った時にでたエラー
Ansibleで使うUbuntuを16.04にしてみたところいくつかエラーが発生した
config.vm.box = "ubuntu/xenial64"
エラーその1
vagrant upしてTASK [setup]で止まる
TASK [setup] ******************************************************************* fatal: [default]: FAILED! => {"changed": false, "failed": true, "module_stderr": "", "module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n", "msg": "MODULE FAILURE", "parsed": false}
原因はUbuntu 16.04ではデフォルトでPython3系が入っているためで、これを2系に変えないとAnsibleは動かない
ということでVagrantfileに以下を追加して対応
config.vm.provision "shell", inline: <<-SHELL sudo apt-get update sudo apt-get install -y python-simplejson SHELL
エラーその2
apt: upgrade=fullでエラーがでる
TASK [common : upgrade a server] *********************************************** fatal: [default]: FAILED! => {"changed": false, "failed": true, "msg": "Could not find aptitude. Please ensure it is installed."}
apt: upgradeの前に以下を追加
- name: install aptitude apt: pkg=aptitude
UITableViewCellのセパレーターを消す
セルのセパレーター消すやつ
override func awakeFromNib() { separatorInset = UIEdgeInsets(top: 0, left: bounds.width, bottom: 0, right: 0) }
RxSwiftでTableView その3
RxTableViewSectionedReloadDataSourceでは複数Sectionに対応できましたが、 Section単位でreloadData()がされてしまっていました。
今回はRxTableViewSectionedAnimatedDataSourceを使ってみます
RxTableViewSectionedAnimatedDataSource
ViewControllerはRxTableViewSectionedReloadDataSourceとほぼ一緒で、dataSourceの型がRxTableViewSectionedAnimatedDataSourceになっただけです。
ViewController
import RxSwift import RxCocoa import RxDataSources class MultiListViewController: UIViewController { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var button1: UIButton! private let viewModel = MultiListViewModel() private let dataSource = RxTableViewSectionedAnimatedDataSource<MySectionModel>() private let disposeBag = DisposeBag() override func viewDidLoad() { dataSource.configureCell = { (_, tableView, indexPath, element) in let cell = tableView.dequeueReusableCellWithIdentifier("Cell1")! cell.textLabel?.text = "\(element) @ row \(indexPath.row)" return cell } viewModel.items.asObservable() .bindTo(tableView.rx_itemsWithDataSource(dataSource)) .addDisposableTo(disposeBag) button1.rx_tap .bindNext { [weak self] in self?.viewModel.change() } .addDisposableTo(disposeBag) } }
ViewModel
dataSourceとbindingさせる配列はAnimatableSectionModelType protocolに対応している必要があります。
更にAnimatableSectionModelTypeのitemはIdentificableType protocolに対応している必要があります。これはitemが変化したかどうか判定するために使われます。
struct MySectionModel { var header: String var items: [Item] } extension MySectionModel : AnimatableSectionModelType { typealias Item = String var identity: String { return header } init(original: MySectionModel, items: [Item]) { self = original self.items = items } } extension String: IdentifiableType { public typealias Identity = String public var identity : Identity { return self } } class MultiListViewModel { let items = Variable<[MySectionModel]>([]) var section1 = MySectionModel(header: "section1", items: ["a", "b", "c"]) var section2 = MySectionModel(header: "section2", items: ["d", "e", "f"]) init () { items.value.append(section1) items.value.append(section2) } func change() { items.value[0] = MySectionModel(header: "section1", items: ["a", "b", "c", "z"]) } }
変更があった場合、内部ではtableViewのperformBatchUpdatesが実行されるので変化のあったcellだけ変更がかかります。
アニメーションを変更したい場合はAnimationContigurationを変更してやればよいです。
dataSource.animationConfiguration = AnimationConfiguration(insertAnimation: .Right, reloadAnimation: .Right, deleteAnimation: .Right)
RxTableViewSectionedAnimatedDataSourceの使い所
- cellの挿入や削除などでアニメーションを入れたい場合
- 変化があった場合にSectionやTableView全体をreloadDataしたくない場合
ただ気になるところが、内部的な変化の判定するために、変更前後の配列の比較を行っているので効率的とはいえません。 利用する側にとっては何も考えずに配列ごと上書きしてしまえばよいのですが、すごい大きい配列になった場合にパフォーマンスとか大丈夫なのかなと懸念がありそうな気がします。