close

[Swift5]広告更新で再描画され座標がずれる件に対処する[考察と結果]

公開日:

前提

1.iPhoneX以降のSafeAreaを考慮したパーツの配置にしている場合

iPhoneX以降を考慮して配置している場合

2.画面内でそのパーツの動きがある

座標が変わらない場合はおそらく問題になりません。
しかし、パーツが動く場合は後述の意図しない座標になる場合があります。

3.viewDidLayoutSubviews()およびConstraintsの再描画が発生

広告表示が変わるなど再描画フラグが立った時に座標が初期化される問題がある

環境:Xcode 11.2.1

SafeAreaを取得して座標を決定する2つの方法

まず前提で説明したSafeAreaの取得方法です。

1.Constraintsで設定する方法

Constraintsで実装した場合も後述のviewDidLayoutSubviews()の再描画で位置が変わる現象は起こりました。

Constraintsで一度きりの処理をする方法は見つからなかったので、今回は触れません。

2.viewDidLayoutSubviews()で取得

viewDidAppear()ではSafeAreaは取得できませんでした。

以下が実際の実装コードです

  var paddingBottom:CGFloat = 0     
override func viewDidLayoutSubviews() {
         super.viewDidLayoutSubviews()
         print("【viewDidLayoutSubviews】")
         NotificationCenter.default.addObserver(self,
         selector: #selector(self.checkSafeArea(notification:)),
         name: UIDevice.orientationDidChangeNotification, object: nil)
         print(paddingBottom)
         //カレンダーボタンの座標(ButtonはStoryBoardで制御)
                 self.calendarButton.frame.origin.x = self.screenSize.width - 70 - 20
                 self.calendarButton.frame.origin.y = self.screenSize.height - self.paddingBottom - 70 - 50//banner50
     }
     //SafeArea取得
     @objc func checkSafeArea(notification: NSNotification){
         if #available(iOS 11.0, *) {//iOS11以降の場合
             let window = UIApplication.shared.keyWindow
 //            paddingTop = window!.safeAreaInsets.top
             paddingBottom = window!.safeAreaInsets.bottom
 //            paddingLeft = window!.safeAreaInsets.left
 //            paddingRight = window!.safeAreaInsets.right
         }
     } 

参考にしたサイトhttps://i-app-tec.com/ios/iphone-safearea.html#2

これでiPhone11でもSafeAreaを空けてボタンを配置できるようになりました。

しかし、これだけでは問題があります。

問題

下の画像のようにボタンをタップすると上下するような作りだった場合、
移動した後に再描画されるとボタンの位置が勝手に戻ってしまいます。

ボタンの初期位置
タップでボタンが上がる
意図しない位置

↑の画像のように一定時間経過後、勝手にボタンが下がって、意図しない位置になってしまうことがあります。

解決方法(試行)

クロージャを使用し、1度だけの処理を実現する

参考になる記事

クロージャの基礎がわかりやすい記事http://programming-beginner-memo.com/?p=556

一度だけ処理考察(Qiita)https://qiita.com/codelynx/items/f0243d631f2448e89026

一度だけ処理考察(Qiita)
https://qiita.com/YusukeHosonuma/items/95315add4004b59e5f00#%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AB%E6%9B%B8%E3%81%8F%E3%81%A8

ViewDidLayoutSubviewsで初回だけ処理を実行する方法https://www.mani-labo.net/entry/swift-lazy-closure

検討したコード1(失敗)

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
内での記述
 private let calendarButtonInit: (() -> ())? = {
             //カレンダーボタンの座標
             self.calendarButton.frame.origin.y = self.screenSize.height - self.paddingBottom - 70 - 50
             return nil
         }()
         calendarButtonInit!() 

実装時エラー

Attribute ‘private’ can only be used in a non-local scope

Lazyは現在使用不可でした。

privateは削除し、実行すると以下のエラーが発生。

実行時エラー

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

一度断念し他の方法を探ります。

検討したコード2(成功)

if文で別の要素の値を取得して座標を反映させる

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
内での記述
 if self.calendar.frame.origin.y == self.screenSize.height{
                 self.calendarButton.frame.origin.y = self.screenSize.height - self.paddingBottom - 70 - 50
             }else{
                 self.calendarButton.frame.origin.y = self.screenSize.height - 330 - self.paddingBottom - 50
                     
             } 

検証したコード3(失敗)

var isFirst = 1 
if isFirst == 1{
             isFirst = 2
             self.calendarButton.frame.origin.y = self.screenSize.height - self.paddingBottom - 70 - 50
             print(self.calendarButton.frame.origin.y)
             print("【isFirst】")
             print(isFirst)
         } 

viewDidLayoutSubviewsは初回描画時に二度以上処理される

このisFirst検証時でわかりました。

上記の例なら、描画時には既にisFirst = 2となっていることになります。

広告を表示している場合はviewDidLayoutSubviewsに初回判定は無意味ということが判明しました。

検証した方法4(失敗)

Constraintsのみ設定した場合

再描画時必ず初期座標が再取得されるため、意図しない位置に移動するため不可。

結果

少し不格好ですが、検証したコード2のみ上手くいきました。

(別の要素の値が取得できなければ詰みでした)

また別の機会にいい方法を考えたいと思います。

Comment

メールアドレスが公開されることはありません。