なんとかするから、なんとかなる

エンジニア関係のことを書きます

iOS UIViewControllerでバックグラウンドへの遷移を検知する

English version below.

はじめに

UIViewControllerにおいて、アプリがバックグラウンドなったときやバックグラウンドに戻ってきたときに処理を行いことはよくあります。

今回はそのやり方を紹介したいと思います。

解決方法

ご存知の通り、UIViewControllerにはバックグラウンドになったときやバックグラウンドから戻ってきたときのデリゲートはありません。

なので、次のようにNotificationCenterに登録するしかありません。

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(viewWillEnterBackground), name: .UIApplicationWillResignActive, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(viewDidEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
        
    NotificationCenter.default.addObserver(self, selector: #selector(viewWillEnterForeground), name: .UIApplicationWillEnterForeground , object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(viewDidBecomeActive), name: .UIApplicationDidBecomeActive , object: nil)
}

@objc func viewWillEnterBackground() {
    // バックグランドへ遷移前に呼ばれる
}
   
@objc func viewDidEnterBackground() {
    // バックグラウンドへ遷移後に呼ばれる
}
    
@objc func viewWillEnterForeground() {
    // バックグラウンドから遷移してくるときに呼ばれる
}
    
@objc func viewDidBecomeActive() {
    // バックグラウンドから遷移してきたときに呼ばれる
}

iOS How to detect the background transition in UIViewController

Introduction

This article shows hot to detect an entering background and activating from background in UIViewController.

Solution

As you know, UIViewController doesn't have a delegate which is called when it will go background and come back from it.

So, the only way to receive those events is register the UIViewController to a NotificationCenter as below.

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(viewWillEnterBackground), name: .UIApplicationWillResignActive, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(viewDidEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
        
    NotificationCenter.default.addObserver(self, selector: #selector(viewWillEnterForeground), name: .UIApplicationWillEnterForeground , object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(viewDidBecomeActive), name: .UIApplicationDidBecomeActive , object: nil)
}

@objc func viewWillEnterBackground() {
    // called when app will enter background
}
    
@objc func viewDidEnterBackground() {
    // called when app entered background
}
    
@objc func viewWillEnterForeground() {
    // called when app will be foreground
}
    
@objc func viewDidBecomeActive() {
    // called when app become active
}

iOS LLDBでViewを操作するTips

English version below

はじめに

今回はデバッグ中にLLDBでViewを操作するために役立つTipsを紹介します。

準備

デバッグを開始して、[Debug View Hierarchy]を開いておきます。 f:id:hopita:20180824213110p:plain

今回はUILabelの情報を変更します。

Viewのメモリを調べる

[Debug View Hierarchy] を開いて、調べたいViewを右クリックして[Print Description of UILabel]を選択すると、次のような情報が表示されます。

Printing description of $12:
<UILabel: 0x7fb65a6075a0; frame = (0 0; 250 20.3333); text = 'あいうえお'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000000975c0>>

上記で表示された中で0x7fb65a6075a0 をコピーしておきます。

メモリからViewの情報を表示する

LLDBから先ほどコピーしておいたメモリを指定して、UILabelの情報を表示させます。

po ((UILabel *) 0x7fb65a6075a0)

結果

<UILabel: 0x7fb65a6075a0; frame = (0 0; 250 20.3333); text = 'あいうえお'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000000975c0>>

先ほどの[Print Description of UILabel]と同じ内容が表示されました。

UILabelのtext(文字)を変更する

UILabelの文字を変更する場合は次のようにやります。

e [[(UILabel *) 0x7fb65a6075a0) setText:@"newText"]

eはexpressionのaliasです。eとしても問題ありません。

UIの変更を即座に反映する

上記の方法では、$0 = "newText"のように出ます。

しかしSimulatorや実機ではまだ反映されていません。

Continueすることで反映させることもできます。

しかしDebugのために即座に反映させましょう。

e (void)[CATransaction flush]

DebugからContinueすることなく、反映されたと思います。

おわりに

LLDBかなり強力なDebugツールです。

もっと使いこなしていきたいですね。

iOS How to solve the black screen when a modal view was dismissed with custom animation.

Introduction

This article shows how to solve the black screen when a modal view was dismissed with custom animation.

When I tried to set an present and modal animation when a modal view was appeared and disappeared.

Though the present animation seemed well, a dismiss animation faced on a problem.

After dismissing a modal view, the screen turned to black and not working anymore.

It seemed the rootViewController was removed from the UIWindow.

In this case I wrote the code as below.

  • **ViewController
func transtionView() {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let myVC = storyboard.instantiateViewController(withIdentifier: "MyVC")    
    myVC.transitioningDelegate = self
    myVC.modalTransitionStyle = .cutom // Here
    self.present(myVC, animation: true)
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return MyPresentAnimation()
    }

    func animationController(forDismissed: UIViewController) ->  UIViewControllerAnimatedTransitioning?
        return MyDismissAnimation()
    }

Solution

Commented out the myVC.modalTransitionStyle = .custom line, it was working fine.

Though I don't know the details of this issues, it seemed that if I set an modal TransitonStyle parameter for .custom.

Maybe I need to implement the delegate function below

func presentationController(forPresented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?

I'm not sure if it is correct or not. I'm going to search and try it.

iOS UIViewControllerのPresentに対してカスタムアニメーションをつけたらブラックアウトしたときの対処法

English Version below

はじめに

UIViewControllerのモーダル表示presentメソッドに独自アニメーションをつけたら、dismiss時に画面が黒くなった(ブラックアウトした)ときの対処方法です。

UIViewControllerでモーダル表示時にアニメーションを指定するときに次のようなコードを書いていました。

  • **ViewController
func transtionView() {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let myVC = storyboard.instantiateViewController(withIdentifier: "MyVC")    
    myVC.transitioningDelegate = self
    myVC.modalTransitionStyle = .cutom //ここ
    self.present(myVC, animation: true)
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return MyPresentAnimation()
    }

    func animationController(forDismissed: UIViewController) ->  UIViewControllerAnimatedTransitioning?
        return MyDismissAnimation()
    }

このようなコードを実行したとき、presentで遷移したときはうまく行っているように見えました。

しかし、dismissで戻ってきたとき、animationが完了後に画面がブラックアウトしました。

UIWindow以外なくなりました。おそらくrootViewControllerがなくなりました。

解決方法

コードのコメントにも書いてあるとおり、myVC.modalTransitionSytleの行をコメントアウトすると正常に遷移しました。

具体的な原因はわかりませんが、おそらくmodalTransitionStyle = .customを指定した場合、 UIViewControllerTransitioningDelegate の以下のメソッドを実装しなければいけない気がします。(試せていないです)

func presentationController(forPresented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?

最後に

原因がわからなくて半日くらい頭を抱えていました。

アニメーションの部分はかなり多様する機能なので、もっと勉強しようと心に決意。

そのうち、アニメーション付与の方法についても紹介したいと思います。

iOS How to scale an image in UIButton.

Introduction

This article shows how to scale an image in UIButton.

When UIButton will be a large scale or small scale by Autolayout engine, an image it was contained in the UIButton is not correspond to the scale normally.

This article will introduce how to solve the problem.

Solution

To solve the proble, you need to set three propertiesas below.

button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill

If you didn't set contentHorizontalAlignment and contentVerticalAlignment, the image would be scaled by image orinal size(content size).

Maybe in iOS in default, imageView conteined in UIButton wouldn't scale up larger than original image size.

So you need to set those values and to be scaled the imageView in UIButton.

And, if you want to make some margin for the image in UIButton.

Set an imageEdgeInsets as below

button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10);

Reference URL

iOS How to get a monospace font for digits in UIFont.

Intoduction

This article shows how to set a monospace font for digits.

A monospace font has same width in each character.

Sometimes, a font which isn't monospace make the design baddly.

Solution

To get the monospace font for digits, Apple already prepare such a method for our developer in UIFont.

class func monospacedDigitSystemFont(ofSize fontSize: CGFloat, weight: UIFont.Weight) -> UIFont

So, we just need to call it.

myLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 17 weight: UIFont.Weight.regular)

Reference URL