【iOS11】UIToolbarの上に乗せたUIButtonやUITextViewのタップイベントが効かない

iOS11対応をしている時にハマったので記録として残しておく。

問題のコード

普通にtoobarの上にボタンを置いているだけ。
iOS10以下なら問題なくボタンのタップイベントを取得できるがiOS11だとボタンのタップが効かなくなる。

  UIToolbar *toolBar = [[UIToolbar alloc]init];
  [self.view addSubview:toolBar];

  UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
  [button setTitle:@"push" forState:UIControlStateNormal];
  [toolBar addSubview:button];

  toolBar.translatesAutoresizingMaskIntoConstraints = NO;
  [toolBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
  [toolBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
  [toolBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
  [toolBar.heightAnchor constraintEqualToConstant:50];

  button.translatesAutoresizingMaskIntoConstraints = NO;
  [button.topAnchor constraintEqualToAnchor:toolBar.topAnchor].active = YES;
  [button.leadingAnchor constraintEqualToAnchor:toolBar.leadingAnchor].active = YES;
  [button.trailingAnchor constraintEqualToAnchor:toolBar.trailingAnchor].active = YES;
  [button.bottomAnchor constraintEqualToAnchor:toolBar.bottomAnchor].active = YES;

改善

toobarにaddSubViewする前に toobarに対して layoutIfNeededを呼んでやればいいみたい。 サブクラスにしている場合でも、クラス内でtoolbarにaddSubViewする前にlayoutIfNeededを呼べば良い。

ちなみにiOS10以下でlayoutIfNeededを呼んでいても特に問題はなかったのでiOS11用にコードを分岐する必要はなさそう。
toolbarのviewの階層を調べてみたが特に違いはなかったので単純にバグのような気がする。

素直にViewとかで代用した方がいいかもしれない。blur効果が欲しいならUIVisualEffectViewとか。

  UIToolbar *toolBar = [[UIToolbar alloc]init];
  [self.view addSubview:toolBar];
  // 追記
  [toolBar layoutIfNeeded];

  UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
  [button setTitle:@"push" forState:UIControlStateNormal];
  [toolBar addSubview:button];

  toolBar.translatesAutoresizingMaskIntoConstraints = NO;
  [toolBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
  [toolBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
  [toolBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
  [toolBar.heightAnchor constraintEqualToConstant:50];

  button.translatesAutoresizingMaskIntoConstraints = NO;
  [button.topAnchor constraintEqualToAnchor:toolBar.topAnchor].active = YES;
  [button.leadingAnchor constraintEqualToAnchor:toolBar.leadingAnchor].active = YES;
  [button.trailingAnchor constraintEqualToAnchor:toolBar.trailingAnchor].active = YES;
  [button.bottomAnchor constraintEqualToAnchor:toolBar.bottomAnchor].active = YES;

一応Viewの階層をのせておく。

layoutIfNeededを呼んでいない時のToolbarのViewの階層

// iOS10.2
(
    "<UIButton: 0x7feac8d84c20; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x608000423020>>"
)

// iOS11.0.1
(
    "<UIButton: 0x7ffc36443830; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x60000043e740>>"
)

layoutIfNeededを呼んだ後のToolbarのViewの階層

なんか違ってて怖い。

// iOS10.2
(
    "<_UIBarBackground: 0x7f9ff6e736d0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x600000220740>>",
    "<UIButton: 0x7f9ff6e746a0; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x600000425ec0>>"
)

// iOS11.0.1
 (
    "<_UIBarBackground: 0x7f8cc9c98f20; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x600000629da0>>",
    "<_UIToolbarContentView: 0x7f8cc9c9a090; frame = (0 0; 0 0); layer = <CALayer: 0x60000062a600>>",
    "<UIButton: 0x7f8cc9c9ce10; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x60000062b380>>"
)

【iOS】UITableViewをスクロールしている時にNSTimerが呼ばれない

UITableViewに限らずUIScrollViewを継承しているクラス全部に言えることだと思うが
以下のような処理だとスクロール中にタイマー処理がスキップされる

- (void)setupTimer {
    NSTimer *timer = [NSTimer timerWithTimeInterval:3.0
                                             target:self
                                           selector:@selector(timerUpdate)
                                           userInfo:nil repeats:YES];
}
- (void)timerUpdate {
  NSLog(@"timer呼ばれた");
}

改善コード

mainRunLoopにタイマーを追加してやれば良い

- (void)setupTimer {

    NSTimer *timer = [NSTimer timerWithTimeInterval:3.0
                                             target:self
                                           selector:@selector(timerUpdate)
                                           userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
- (void)timerUpdate {
  NSLog(@"timer呼ばれた");
}

【Fastlane】deliverを使用してストア情報を更新する

環境

  • Xcode 8.3.2
  • Fastlane 2.45.0

前提条件

  • bundlerを使用してfastlaneをインストー

セットアップ

まずは以下のコマンドを実行して環境を整えます

bundle exec fastlane deliver init

Appfileに記述されているアカウント情報を確認しているようです。
確認出来ない場合は入力を促されると思います

確認される項目
  • AppleId (メールアドレス)
  • password (一度入力するとkeychainに保存される)
  • Bundle Identifier

また、確認されたBundle Identifierを使用して作成したストアページがない場合はエラーが出ます 。なので作ってない場合は先に作りましょう。

初期化後のディレクトリ構造の例
fastlane/  
┣ AppFile  
    ┣ DeliverFile   
    ┣ FastFile 
    ┣ metadata
        ┣ copyright.txt
        ┣ en-US
             ┣ description.txt
             ┣ keywords.txt
             ┣ marketing_url.txt
             ┣ name.txt
             ┣ privacy_url.txt
             ┣ promotional_text.txt
             ┣ release_notes.txt
             ┣ subtitle.txt
             ┣ support_url.txt
        ┣ ja
             ┣ description.txt
             ┣ keywords.txt
             ┣ marketing_url.txt
             ┣ name.txt
             ┣ privacy_url.txt
             ┣ promotional_text.txt
             ┣ release_notes.txt
             ┣ subtitle.txt
             ┣ support_url.txt
        ┣ primary_category.txt
        ┣ primary_first_sub_category.txt
        ┣ primary_second_sub_category.txt
        ┣ review_information
             ┣ demo_password.txt
             ┣ demo_user.txt
             ┣ email_address.txt
             ┣ first_name.txt
             ┣ last_name.txt
             ┣ notes.txt
             ┣ phone_number.txt
        ┣ secondary_category.txt
        ┣ secondary_first_sub_category.txt
        ┣ secondary_second_sub_category.txt
        ┣ trade_representative_contact_information
             ┣ address_line1.txt
             ┣ address_line2.txt
             ┣ city_name.txt
             ┣ country.txt
             ┣ email_address.txt
             ┣ first_name.txt
             ┣ is_displayed_on_app_store.txt
             ┣ last_name.txt
             ┣ phone_number.txt
             ┣ postal_code.txt
             ┣ state.txt
             ┣ trade_name.txt
    ┣ screenshots
          ┣ en-US
              ┣ xxxxx.png 
          ┣ README.txt

今のストアの状態をそのまま取ってきてくれるみたいなので、後から導入するのも楽そうです。

DeliverFile

DeliverFileに変更したい項目を記載してコマンドを叩きます。

DeliverFileの例
# リリースノート
release_notes({
  'en-US' => "Try the new version",
  'ja' => "新しいバージョンをお試し下さい"
})

# アプリの説明
description({
  'en-US' => "English Description\nEnglish Description\nEnglish Description\n",
  'ja' => "日本語の説明文\n日本語の説明文\n日本語の説明文\n"
})

# キーワード
keywords(
  'en-US' => "Keyword1,Keyword2",
   'ja' => "キーワード1,キーワード2"
)

# HTMLで内容確認を行わない場合'true'
# デフォルトは `false`なので内容確認のHTMLが表示されます
# force true

こんな感じで変更したい項目を記述していきます。
まぁ後から入れた場合はほぼほぼ'release_notes'だけになると思いますが。。
変更したくない項目は記載しなければ大丈夫です。

以下のコマンドでDeliverFileの内容でitunesConnectを更新します。

bundle exec fastlane deliver

現在のitunesConnectの情報にmetadata配下を更新したい場合は以下のコマンドを実行します。 itunesConnectを更新したあと実行するといいと思います。

bundle exec fastlane deliver download_metadata

スクリーンショット

f:id:y-hryk:20170710113858p:plain

上記のようにscreenshots配下におきます。

アップロードのルール
  • 並び順はフォルダ内の順番と同じ
  • サイズは自動判定されて適切な場所にアップロードされる (5.5インチとか4.7インチとかの話です)

名前の付け方で上手く運用できそう。

【Fastlane】The following build commands failed CopySwiftLibs エラーが出た時

ローカル環境では特に問題ないけど、ビルドマシンのmac-miniにsshでログインしてgymを実行したり mac-miniのjenkinsからgymを実行すると 以下エラー等が出て困った。

  • The following build commands failed:CopySwiftLibs
  • The following build commands failed:CodeSign

ビルドする端末のキーチェーンアクセスを開いて該当する証明書をログインではなく、システムに入れればいいみたい。

余談ですが、上記エラーと一緒に以下のエラーが出ていました。

[16:37:53]: [31mExit status: 65[0m
[16:37:53]: [33m📋  For a more detailed error log, check the full log at:[0m
[16:37:53]: [33m📋  /Users/yamanaka/Library/Logs/gym/MEME-Run-iOS-MEME-Run-iOS.log[0m
[16:37:53]: [31mFound multiple versions of Xcode in '/Applications/'[0m
[16:37:53]: [31mMake sure you selected the right version for your project[0m
[16:37:53]: [31mThis build process was executed using '/Applications/Xcode.app'[0m
[16:37:53]: [33mIf you want to update your Xcode path, either[0m
[16:37:53]: 
[16:37:53]: - Specify the Xcode version in your Fastfile
[16:37:53]: ▸ [35mxcversion(version: "8.1") # Selects Xcode 8.1.0[0m
[16:37:53]: 
[16:37:53]: - Specify an absolute path to your Xcode installation in your Fastfile
[16:37:53]: ▸ [35mxcode_select "/Applications/Xcode8.app"[0m
[16:37:53]: 
[16:37:53]: - Manually update the path using
[16:37:53]: ▸ [35msudo xcode-select -s /Applications/Xcode.app[0m
[16:37:53]: 

僕の環境だと端末内に複数バージョンのXcodeが入っていたため、上記エラーの通りXcodeの指定がちゃんとできていないと思ってこの辺りを中心に調査して盛大に時間をロスしました。(ビルドに使用するXcodexcode_selectアクションで指定できます。 )

xcode_select "/Applications/Xcode.app"

上記のアクションを実行するようにしてFastlaneのビルドログを見ると以下のように使用するXcodeのpathが がちゃんと指定されているのにも関わらず Xcode指定してくれエラーが出続けました。

+----------------------+-------------------------------------------------------------+
|                               Summary for gym 2.36.0                               |
+----------------------+-------------------------------------------------------------+
| scheme               | Demo                                                        |
| export_method        | enterprise                                                  |
| configuration        | AdHoc                                                       |
| use_legacy_build_api | false                                                       |
| output_directory     | ./fastlane/ipa/enterprise/                                  |
| project              | ./Demo.xcodeproj                                            |
| destination          | generic/platform=iOS                                        |
| output_name          | Demo                                                        |
| build_path           | /Users/xxxxxxx/Library/Developer/Xcode/Archives/2017-06-07 |
| clean                | false                                                       |
| silent               | false                                                       |
| skip_package_ipa     | false                                                       |
| buildlog_path        | ~/Library/Logs/gym                                          |
| xcode_path           | /Applications/Xcode.app                                     |
+----------------------+-------------------------------------------------------------+

結局タイトルのエラーを解決したらこのXcode指定してくれエラーは消えて正常にビルドできるようになりました。

端末に複数バージョンXcodeが入っていて、かつThe following build commands failedが出ている場合に一緒に上記のエラーが出ているような気がします。

Exit status: 65- Found multiple versions of Xcode in '/Applications/' · Issue #8327 · fastlane/fastlane · GitHub

このissuesとかもXcodeの指定の問題じゃなくてThe following build commands failedエラーが出ていることが原因なんじゃないかなー。。

一枚のUIImageViewで画像の入れ替えアニメーションを行う

コードは以下の通りです

let transition = CATransition()
transition.duration = 0.75
transition.type = kCATransitionFade

self.myImageView.image = UIImage(named: "image2.png")
self.myImageView.layer.add(transition, forKey: nil)

また以下のコードでアニメーションの終了タイミングを取得することができます

CATransaction.begin()
      
CATransaction.setCompletionBlock({ 
      // 終了時に呼ばれる
})
            
let transition = CATransition()
transition.duration = 0.75
transition.type = kCATransitionFade
self.thumbnailImage.image = UIImage(named: "image2.png")
self.thumbnailImage.layer.add(transition, forKey: nil)

CATransaction.commit()

delegateも用意されている

class Myclass {

  func startAnimation() {
      let transition = CATransition()
      transition.duration = 0.75
      transition.type = kCATransitionFade
      transition.delegate = self
      self.thumbnailImage.image = UIImage(named: "image2.png")
      self.thumbnailImage.layer.add(transition, forKey: nil)
  }

}
extension Myclass: CAAnimationDelegate {

 func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        print("アニメーション終了")
 }

PHAssetからファイル名や拡張子を取得する

PHAssetからファイル名を取得しようとしたらちょっと苦労したのでメモ

PHAssetのメンバにはそれっぽいものがなかった。
結論を言うと

  • Private Apiで取得
  • PHImageManager経由で取得

上記のどちらかのようだ。

Private Apiで取得

// 'asset'は PHAssetのインスタンス
let filename = asset.value(forKey: "filename")

PHImageManager経由で取得

PHImageManagerの関数の戻り値から取得できる
画像とビデオでそれぞれ取得方法が違った

// 'asset'は PHAssetのインスタンス

 switch asset.mediaType {
     case .image:

        let option = PHImageRequestOptions()
        option.deliveryMode = .highQualityFormat

        PHImageManager.default().requestImage(for: asset,
                                      targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
                                      contentMode: .aspectFill,
                                      options: option) { (image, info) in
                                                        
                                         if let url = info?["PHImageFileURLKey"] as? URL {
                                                 print(url.lastPathComponent)
                                                 print(url.pathExtension)
                                         }         
         }
        
     case .video:

        let option = PHVideoRequestOptions()
        option.deliveryMode = .highQualityFormat
                
        PHImageManager.default().requestAVAsset(forVideo: asset,
                                      options: option,
                                      resultHandler: { (avAsset, audioMix, info) in
                                                        
                                           if let tokenStr = info?["PHImageFileSandboxExtensionTokenKey"] as? String {
            
                                              let tokenKeys = tokenStr.components(separatedBy: ";")
                                              let urlStr = tokenKeys.filter { $0.contains("/private/var/mobile/Media") }.first
                                                            
                                                   if let urlStr = urlStr {
                                                        if let url = URL(string: urlStr) {
                                                              print(url.lastPathComponent)
                                                              print(url.pathExtension)
                                                        }
                                                   }
                                           }
          })

     default: break
}

Raspberry Pi3にCentOS7をインストール

どうやらRaspberry piCentOSを入れることができるようなので 手順を残しておく。

必要なもの

  • PC (mac想定)
  • Raspberry pi3
  • MicroSD (16GB以上あれば大丈夫だと思う)
  • SDカードスロット (pcについてればそれでもいい)
  • LANケーブル
  • HDMIケーブル
  • ACアダプタ
あったほうが便利 (なくてもルーターからローカルIPを調べれば sshで操作可能 )
  • モニター
  • USBキーボード

CentOSをインストールする

まずはRaspberry Pi3用のCentOS7をダウンロードする

https://wiki.centos.org/Download

上記リンクからダウンロードできる
リンク先からRaspberry Pi3用を探してダウンロードする

2017年2月現在では以下のファイル名 CentOS-Userland-7-armv7hl-Minimal-1611-RaspberryPi3.img.xz

ダウンロード後、上記を解凍する アプリを使ってもいいしコマンドでも可

コマンドの場合

#homebrewでインストールできる
$brew install xz
#以下で解凍
xz -d CentOS-Userland-7-armv7hl-Minimal-1611-RaspberryPi3.img.xz

MicroSDに書き込みます
pcにMicroSDを指す

#マウントされてるMicroSDを調べる
$diskutil list


/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *121.3 GB   disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:          Apple_CoreStorage                         120.5 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.1 MB   disk0s3
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           *120.1 GB   disk1
                                 Logical Volume on disk0s2
                                 2117A7C6-F20D-4E5F-92D2-0B352281C440
                                 Unlocked Encrypted
/dev/disk2
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.8 GB    disk2
   1:             Windows_FAT_32 boot                    66.1 MB    disk2s1
   2:                      Linux                         15.7 GB    disk2s2
#一旦アンマウントする (※`disk2s1`を指定)
$diskutil umount /dev/disk2s1
#書き込む 結構時間かかるので待つ(※`disk2s1`ではなく`disk2`を指定)
$sudo dd if=CentOS-Userland-7-armv7hl-Minimal-1611-RaspberryPi3.img of=/dev/disk2 bs=8192; sync

393216+0 records in
393216+0 records out
3221225472 bytes transferred in 1423.262396 secs (2263269 bytes/sec)

Rasberry pi の起動とログイン

MicroSDカードをセット。LANなどを差し込んで最後にACアダプタを差す
しばらく待つと起動するのでログインする
キーボード、モニターがない場合はローカルipを調べてsshでログインしても良い

#初期値は以下
login: root
password: centos

sshでログイン

# centosでipを調べる
$ip a
#mac側でsshでログインする
$ssh root@192.168.xxx
password 

タイムゾーンの設定

# タイムゾーンを`Asia/Tokyo`に変更する
$timedatectl set-timezone Asia/Tokyo
# 確認
$timedatectl status
      Local time: 日 2017-02-26 22:56:44 JST
  Universal time: 日 2017-02-26 13:56:44 UTC
        RTC time: n/a
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: n/a

Rootパーティションのサイズ拡張

デフォルトではSDカードの容量を全て使用していないため拡張する必要があるらしい
これについては root/README に書いてある

#  root/ 配下のREADME

If you want to automatically resize your / partition, just type the following (as root user):
/usr/local/bin/rootfs-expand

For wifi on the rpi3, just proceed with those steps : 

curl --location https://github.com/RPi-Distro/firmware-nonfree/raw/master/brcm80211/brcm/brcmfmac43430-sdio.bin > /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin

curl --location https://github.com/RPi-Distro/firmware-nonfree/raw/master/brcm80211/brcm/brcmfmac43430-sdio.txt > /usr/lib/firmware/brcm/brcmfmac43430-sdio.txt

systemctl reboot
#  READMEに書いてある通りに以下を実行
$/usr/local/bin/rootfs-expand

Extending partition 3 to max size ....
/usr/bin/growpart: 行 170: シリンダ数*416*: 構文エラー: オペランドが予期されます (エラーのあるトークンは "シリンダ数*4、16*")
Resizing ext4 filesystem ...
resize2fs 1.42.9 (28-Dec-2013)
The filesystem is already 524288 blocks long.  Nothing to do!

Done.

どうやらシェルのLANGの設定の問題らしい。

$locale

LANG=ja_JP.UTF-8

上記のようになっていたら

$export LANG="en_US.UTF-8"

で変更。もう一度

$/usr/local/bin/rootfs-expand

終わったら戻しておく

$export LANG="ja_JP.UTF-8"

再起動した後に確認する

$reboot

#拡張されているか確認
$df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/root         14G  2.4G   11G   19% /
devtmpfs         459M     0  459M    0% /dev
tmpfs            463M     0  463M    0% /dev/shm
tmpfs            463M   12M  451M    3% /run
tmpfs            463M     0  463M    0% /sys/fs/cgroup
/dev/mmcblk0p1   500M   49M  452M   10% /boot
tmpfs             93M     0   93M    0% /run/user/0

パッケージの更新

最後にyumを利用してシステムにインストールされているパッケージのバージョンアップを行う

$yum update -y

参考