Swiftで使える便利な小ワザ25選【iOSアプリ開発】

Swiftで使える便利な小ワザ25選【iOSアプリ開発】
Swiftで使える便利な小ワザ25選【iOSアプリ開発】

Swiftで使える便利な技をご紹介。実際にアプリ開発で役立つ小技を、忘備録的にまとめました。

CrashlyticsのdSYMを登録したい

FirebaseのCrashlyticsにて「不足している必須のdSYMをアップロードしてください」と表示される問題の解決方法。

  1. オーガナイザーからArchiveしたパッケージをFinderで表示
  2. xcarchiveファイルを「Show Package Contents」で中身を覗き、dSYMsフォルダをターミナルに貼り付けるなどして、パス /path/dSYMs を取得する
  3. たとえば HogeProj があった場合、プロジェクトのディレクトリへ移動して以下のコマンドを実行する

shell
./Pods/FirebaseCrashlytics/upload-symbols -gsp HogeProj/GoogleService-Info.plist -p ios /path/dSYMs

保存したファイルをファイル.appに表示させたい

何かのファイルを FileManager を使って documentDirectory に保存しても、デフォルトでは ファイル.app で表示されない。

以下の設定をInfo.plistに追加することで、ファイル.app に表示される。

UIFileSharingEnabled (Application supports iTunes file sharing)LSSupportsOpeningDocumentsInPlace (Supports opening documents in place) をそれぞれ YES に設定する。

UIBarButtonItemの画像サイズが大きくなってしまう

次のプログラムで UIBarButtonItem を作ってしまうと、この画像のようにボタンのイメージが大きくなり崩れてしまうことがある。

swift
let setting = UIBarButtonItem(image: UIImage(named: "gear"), style: .plain, target: self, action: #selector(clickedSettingButton(_:)))
self.navigationItem.leftBarButtonItems = [setting]

少し面倒だが、この問題を修正するには customViewUIButton を入れて、AutoLayout を設定することで解決できる。

swift
let settingBtn = UIButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
settingBtn.addTarget(self, action: #selector(clickedSettingButton(_:)), for: .touchUpInside)
settingBtn.setImage(UIImage(named: "gear"), for: .normal)
let setting = UIBarButtonItem(customView: settingBtn)
setting.customView?.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
setting.customView?.heightAnchor.constraint(equalToConstant: 24.0).isActive = true

self.navigationItem.leftBarButtonItems = [setting]

キレイにボタンをおさめることができた。

キーボードを表示した時、Viewが隠れないようにしたい

IBOutletNSLayoutConstraint を図のように、特定のViewのボトムラインに結びつける。

IBOutlet で結び付けられた NSLayoutConstraint は、コードで簡単に値を変えることができるようになった。下のプログラムでは、キーボードの高さと、表示アニメーションのdurationを取得して、UIView.animate でキーボードと同じ動きでViewが移動するようにアニメーションをかけている。
swift
class ViewController: UIViewController {

    @IBOutlet weak var bottomLayoutConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    
    @objc func keyboardWillShow(notification: NSNotification) {
        guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
            return
        }
        guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else {
            return
        }

        self.bottomLayoutConstraint.constant = -keyboardSize.height
        
        UIView.animate(withDuration: duration, animations: { () -> Void in
            self.view.layoutIfNeeded()
        })
    }
    
    @objc func keyboardWillHide(notification: NSNotification) {

        guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else {
            return
        }
        self.bottomLayoutConstraint.constant = 0
        
        UIView.animate(withDuration: duration, animations: { () -> Void in
            self.view.layoutIfNeeded()
        })
    }
}

このプログラムではオブザーバーを削除していないため、注意しておく。

Safe Areaのマージンを取得したい

iOS11から利用できる self.safeAreaInsets を使って取得できる。次のようにViewにextensionを定義しておくと便利。使う時は self.view.csSafeAreaInsets.top のようにできる。

swift
extension UIView {
    var csSafeAreaInsets: UIEdgeInsets {
        if #available(iOS 11.0, *) {
            return self.safeAreaInsets
        } else {
            return .zero
        }
    }
}

画面の回転イベントを検知したい

画面回転時のイベントをフックするには UIDevice.orientationDidChangeNotification をオブザーバー登録する必要がある。向きが変わった時だけ処理をさせたい場合は、下のプログラムのように lastOrientation などで以前の状態を保持しておく工夫が必要である。

swift
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(orientationDidChange(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
}

var lastOrientation: UIDeviceOrientation?

@objc func orientationDidChange(_ notification: NSNotification) {
    let device = UIDevice.current
    if device.orientation.isLandscape {
        if lastOrientation != nil && !lastOrientation!.isLandscape {
            print("横向きに変わった")
        }

    } else if device.orientation.isPortrait {
        if lastOrientation != nil && !lastOrientation!.isPortrait {
            print("縦向きに変わった")
        }
    }
    lastOrientation = device.orientation
}

UIViewControllerのクラス名を取得したい

次のように UIViewController を拡張しておくことで、他のクラスで UIViewController が渡された時にクラス名で条件分岐できるようになる。

swift
extension UIViewController {
  var className: String {
      return String(describing: type(of: self))
  }
}

class hoge {
  func doSomething(rootViewController:UIViewController)
  
      let className = rootViewController.className
  
      switch className {
      case "HogeViewController":
          print("From HogeViewController")
          break
      case "FugaViewController":
          print("From FugaViewController")
          break
      default:
          break
      }
  }
}

同じクラスタイプのViewを一括で操作したい

たとえば、UISegmentedControlがself.viewの中にたくさんあったとして、それらをまとめて処理したい場合は次のように書くことができる。

swift
for v in view.subviews {
    if v.isKind(of: UISegmentedControl.self) {
        let sc = v as! UISegmentedControl
        if #available(iOS 13.0, *) {
            sc.selectedSegmentTintColor = .pink
        } else {
            // Fallback on earlier versions
        }
        sc.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .normal)
        sc.backgroundColor = .black
    }
}

UIImageViewやUIButtonをtintColorで色を変えたい

通常、UIImageViewやUIButtonの画像の色は、tintColorを指定しても変えることができない。UIImage.RenderingMode.alwaysTemplateで画像をα値だけでレンダリングするように設定してあげればtintColorが効くようになる。

swift
let image = UIImage(named: "hoge")?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
button.setImage(image, for: .normal)
button.tintColor = .red

ナビゲーションバーの色を変えたい

ナビゲーションバーの色は、それぞれ次のように定義されている。

文字の色は、NSAttributedString を使って設定する。

swift
// ナビゲーションバーのタイトル
self.navigationItem.title = "ランキング"

// ナビゲーションバーのタイトル色
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]

// ナビゲーションバーの背景色
self.navigationController?.navigationBar.barTintColor = .questionAreaBackground

let closeItem = UIBarButtonItem(title: "x", style: .plain, target: self, action: #selector(closeWindow))
// ボタンアイテムの色
closeItem.setTitleTextAttributes([
                                    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 24),
                                    NSAttributedString.Key.foregroundColor: UIColor.white],
    for: .normal)
self.navigationItem.rightBarButtonItem = closeItem

ちなみに UIBarButtonItemtintColor でも変ることができる。

swift
closeItem.tintColor = .white

また、ナビゲーションバーの色が少し薄い場合は、次のようにして対処する。

swift
self.navigationController?.navigationBar.isTranslucent = false

テーブルセルの背景色を変えたい

UITableViewCell の背景色は、次のようにUITableViewのdelegateで変える。
swift
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cell.backgroundColor = .black
}

MD5ハッシュ値の生成したい

CommonCrypto モジュールをインポートする必要がある。Stringextension しているので、使う時は "hogehoge".md5 のようにして変換することができる。
swift
import Foundation
import CommonCrypto

enum CryptoAlgorithm {
    case MD5, SHA1, SHA224, SHA256, SHA384, SHA512
    
    var digestLength: Int {
        var result: Int32 = 0
        switch self {
        case .MD5:      result = CC_MD5_DIGEST_LENGTH
        case .SHA1:     result = CC_SHA1_DIGEST_LENGTH
        case .SHA224:   result = CC_SHA224_DIGEST_LENGTH
        case .SHA256:   result = CC_SHA256_DIGEST_LENGTH
        case .SHA384:   result = CC_SHA384_DIGEST_LENGTH
        case .SHA512:   result = CC_SHA512_DIGEST_LENGTH
        }
        return Int(result)
    }
}
extension String {
    var md5:    String { return digest(string: self, algorithm: .MD5) }
    var sha1:   String { return digest(string: self, algorithm: .SHA1) }
    var sha224: String { return digest(string: self, algorithm: .SHA224) }
    var sha256: String { return digest(string: self, algorithm: .SHA256) }
    var sha384: String { return digest(string: self, algorithm: .SHA384) }
    var sha512: String { return digest(string: self, algorithm: .SHA512) }

    func digest(string: String, algorithm: CryptoAlgorithm) -> String {
        var result: [CUnsignedChar]
        let digestLength = Int(algorithm.digestLength)
        if let cdata = string.cString(using: String.Encoding.utf8) {
            result = Array(repeating: 0, count: digestLength)
            switch algorithm {
            case .MD5:      CC_MD5(cdata, CC_LONG(cdata.count-1), &result)
            case .SHA1:     CC_SHA1(cdata, CC_LONG(cdata.count-1), &result)
            case .SHA224:   CC_SHA224(cdata, CC_LONG(cdata.count-1), &result)
            case .SHA256:   CC_SHA256(cdata, CC_LONG(cdata.count-1), &result)
            case .SHA384:   CC_SHA384(cdata, CC_LONG(cdata.count-1), &result)
            case .SHA512:   CC_SHA512(cdata, CC_LONG(cdata.count-1), &result)
            }
        } else {
            fatalError("Nil returned when processing input strings as UTF8")
        }
        return (0..<digestLength).reduce("") { $0 + String(format: "%02hhx", result[$1])}
    }
}

参考 Swift4でハッシュ値の計算(MD5,SHA1,SHA256,etc.)

iOSの設定アプリへアクセスしたい

iOSの設定アプリの中の、自分のアプリの設定画面へショートカットできる。

swift
if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
   UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

数秒後に遅らせて実行させたい

DispatchQueue.main.asyncAfter を使えば、次のように遅延処理を簡単に書くことができる。
swift
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  // 0.1秒後にしたい処理をここに書く
  ....
}

メインスレッドで実行したい

非同期処理の後にViewなどのUIを操作しようとするとクラッシュする。UIはメインスレッドで実行しなければならないからだ。DispatchQueue.main.async を使ってメインスレッド内で実行するようにしよう。

swift
if Thread.isMainThread {
  print("MainThreadである")
} else {
  print("MainThreadではない")
}

DispatchQueue.main.async {
  // ここでviewなどを操作する
  ...
}

クロージャでコールバック処理したい

ネットワーク越しに非同期でデータを取ってきて、その後にViewなどにデータを反映したい場合によく使うワザ。completion(Int) -> Void でクロージャ定義する。ネットワーク処理などを書いて、成功したら completion(rank) で呼び出す。

swift
func fetchYourRank(leaderboardId:String, completion: @escaping (Int) -> Void){
  
    let leaderBoard : GKLeaderboard = GKLeaderboard()

    ....
  
    leaderBoard.loadScores(completionHandler: {
        scores, error in
        if (error == nil) {
            let score = leaderBoard.localPlayerScore!
            let rank = score.rank

            completion(rank)
        }
    })
}

使う側はこんな感じ。引数にクロージャを定義することで分かりやすいコールバック処理を書くことができる。

fetchYourRank(leaderboardId: leaderboardId, completion: { rank in
    self.yourLankLabel.text = "あなたの順位は\(rank)位です"
})

画面の向きが変わったらイベントを検知したい

swift
override func viewDidLoad() {
    super.viewDidLoad()

    ...

    NotificationCenter.default.addObserver(self, selector: #selector(orientationDidChange(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
}

@objc func orientationDidChange(_ notification: NSNotification) {
    let device = UIDevice.current
    if device.orientation.isLandscape {
          print("横向き")

    } else if device.orientation.isPortrait {
          print("縦向き")
    }
}
UIDeviceOrientation には isFlatisValidInterfaceOrientation などもあることに注意。

端末がiPadかどうか判定したい

UIDevice.current.userInterfaceIdiomで判別できる。三項演算子と合わせるとスマートに書ける。
swift
let h = (UIDevice.current.userInterfaceIdiom == .pad) ? 90 : 50

デバッグ時とリリース時で動作を変えたい

デバッグ時とリリース時で動作を変えるには、次のようにする。

swift
#if DEBUG
    print("デバッグ時のみ実行")
#else
    print("リリース時のみ実行")
#endif

ただし、このDEBUGの定義を使えるようにするため、Builid SettingsOther Swift FlagsでDebugの項目のみに-D DEBUGと指定する必要がある。

必ずDebugの項目のみにパラメータを入れるようにしよう。

シングルトンを実装したい

次のように static を使って宣言する。 使う時は Hoge.shared.doSomething() のように shared を経由して関数を呼び出す。init をオーバーライドして private にしておくと shared 忘れがなくてよい。

swift
class Hoge {

    static let shared = Hoge()    

    private override init() {
        super.init()
    }

    func doSomething() {
        ...
    }
}

UIColorに独自カラーを追加したい

こんな感じでUIColorを拡張する。

swift
extension UIColor {
    class var hogeColor: UIColor {
        return UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1)
    }
}

ポイントは関数ではなく変数として定義してclassを指定する。すると次のようにシステムカラーと同じような扱いで簡単に指定することができる。

textLabel.textColor = .textLightColor

UITalbeViewの最下部にスクロールしたい

最下部にスクロールするにはscrollToRowを使うが、tableView.reloadData() 後すぐに実行すると正しくスクロールできない。タイマーでタイミングを少しずらすことでうまくいった。

swift
//tableViewに追加する処理をした後
tableView.reloadData()

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    self.tableView.scrollToRow(at: IndexPath(row: self.items.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: true)
}

メソッドを一度だけ実行したい

viewDidLayoutSubviewsなど何度も呼び出されてしまうメソッド内で、一度だけ実行したい処理があった場合に以下の書き方をしていた。これだと毎回フラグを宣言、条件分岐するのは面倒だし、可読性がよくないなと思っていた。

swift
var isFirst = false

override func viewDidLayoutSubviews() { // updateLayout
  if !isFirst {
     isFirst = true
    // 一度だけ実行したい処理を書く
  }  
}

クロージャーとlazyを使ったスマートな書き方を見つけたので、参考にさせていただいた。 UIView などでインスタンス単位で一度だけ実行したい初期化コードの考察 - Qiita

swift
private lazy var onceUpdateLayout: (()->())? = {
  // 一度だけ実行したい処理を書く
    return nil
}()

override func viewDidLayoutSubviews() { // updateLayout
  onceUpdateLayout?()
}

こちらの方が可読性は上がる。他にもいろいろな書き方がいろいろあるようだ。クロージャー(Closures)は名前のない関数で変数として扱われる。lazy(lazy stored property)は、参照されるときにはじめて初期値が設定されるプロパティ。

UILongPressGestureRecognizerは2回呼ばれる

swift
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPressedButton))
button.addGestureRecognizer(longPressRecognizer)

こんな感じで登録したlongPressedButton関数は2回呼ばれる。アラートなど呼び出す時は二度呼ばれてしまうので注意が必要。UIGestureRecognizerのstateで下記のように切り分ける。

swift
@objc func longPressedButton(sender:UIGestureRecognizer) {

    if (sender.state == .began){
      print("長押し開始のタイミング")      
      /*
      何かアラート処理を書いたり
      */
    } else if (sender.state == .ended) {
      print("長押し終了のタイミング")      
    }
}

メソッドチェーンの返り値を使わない時のwarningを消したい

メソッドチェーンを実装しようとした時に、返り値を使わない場合が多々ある。Xcodeは関数の返り値を使わないと丁寧にwarningを出してくれるが、warningだらけになってしまうので対処したかった。結論としては@discardableResultアノテーションを付ければよいだけ。

swift
@discardableResult
func vibrate() -> Self {
    AudioServicesPlaySystemSound(1519)
    return self
}

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム