一部をくり抜いたオーバーレイを表示する方法

背景の一部を表示させたまま、半透明のオーバーレイを表示させたい事があったので、その時の実装した内容のメモです📝

具体的には下記のgifのような挙動の実現方法です。
ボタンをタップすると、背景に表示された緑の四角が見えるようにくり抜かれたオーバーレイを表示して、オーバーレイのどこかをタップすると、それが消えるというもの。

コードはこちらに置いてます。

f:id:muchan611:20191001002450g:plain

一部をくり抜いたオーバーレイは OverlayClippedViewController で実装していて、コードはこちら

private lazy var backgroundLayer: CALayer = {
    let clippedViewWidth: CGFloat = 220
    let clippedViewHeight: CGFloat = 220
    let backgroundLayer = CALayer()
    backgroundLayer.bounds = view.bounds
    backgroundLayer.position = view.center
    backgroundLayer.backgroundColor = UIColor.black.cgColor
    backgroundLayer.opacity = 0.5

    let maskLayer = CAShapeLayer()
    maskLayer.bounds = backgroundLayer.bounds
    let clippingViewRect =  CGRect(x: (view.bounds.width - clippedViewWidth) / 2, y: 190, width: clippedViewWidth, height: clippedViewHeight)
    let path =  UIBezierPath(roundedRect: clippingViewRect, cornerRadius: 5.0)
    path.append(UIBezierPath(rect: maskLayer.bounds))

    maskLayer.fillColor = UIColor.black.cgColor
    maskLayer.path = path.cgPath
    maskLayer.position = view.center
    maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
    backgroundLayer.mask = maskLayer
    return backgroundLayer
}()

maskLayer が塗りつぶしを指定するLayerで、 maskLayer.fillRule = CAShapeLayerFillRule.evenOdd が実際にくり抜く(塗りつぶさない)指定をしている箇所ですね。

半透明のLayerである backgroundLayer のmaskにくり抜き用のmaskLayerを指定する事で、該当箇所がくり抜かれた状態で表示できます✂️

あまり画像をクリップする等しないので、また同じような事を実現したくなった時のための備忘録でした✍️

DateFormatterを汎用化する

9月は山登りしたりテント泊したり、遅めの夏休みということで海外旅行🏝したり、リフレッシュできて満足しているmuchanです。
仕事とプライベートで割と慌ただしくしていた分、体調が心配だったけれども、毎日DHCのマルチビタミンを飲んでうがい手洗いをしていたら、風邪引くこともなく全力投球できました。やっぱり予防は大事ですね💁‍♀️

今回は割と自分のメモに近いですが、DateFormatterを汎用化する話。
DateをStringへ変換する際等によく利用するDateFormatterですが、DateFormatterを生成が重いという話をよく聞きますよね。計測している記事が参考になります👀

日時をDateの形で持っておいて、View側で日時を文字列に変換したい場合などには、ViewController側でDateFormatterを保持したりしますが、割と同じ形式の文字列に変換する事が多かったので、シングルトンで保持してどこからでも利用できるようにしました。

こんな感じ。

class MyDateFormatter {
    static let shared = MyDateFormatter()
    private let yyyyMMddDateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "yyyy/MM/dd"
        return dateFormatter
    }()

    private let HHmmDateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "HH:mm"
        return dateFormatter
    }()

    func formatToyyyyMMdd(from date: Date) -> String {
        return yyyyMMddDateFormatter.string(from: date)
    }

    func formatToHHmm(from date: Date) -> String {
        return HHmmDateFormatter.string(from: date)
    }
}

利用する際は、以下のようになります。

let dateString = MyDateFormatter.shared.formatToyyyyMMdd(from: date)

よく使う形式のDateFormatterについては、シングルトンにして各処理から呼び出すようにする事で生成を1度で済ませることができ、処理を効率化できますね🙌

カスタムCellを使ったUITableView実装テンプレート

もう8月も終わりですね。夏の北アルプス登山は最高なのですが、今年はまだ行けてなくてウズウズしてます⛰
お気持ちが高まって、先週テント泊の練習に行ってきました。テント、最高です🏕

さて本題ですが、タイトル通りです。 TableViewを実装するシーンがよくあるので、コピペで使えるようにテンプレを作ってみました。 Xibで用意したカスタムCellを表示します。

f:id:muchan611:20190824150311p:plain

ソースコードはこちら にあるので、気になった方は覗いてみてください。

実装内容を簡単に説明していきます。

UITableViewと制約追加

class CustomCellListViewController: UIViewController {
    private var sampleItems = [CustomCellItem]()
    private let tableView = UITableView(frame: .zero)
    
    override func viewDidLoad() {
        super.viewDidLoad()

        title = "CustomCellListViewController"
        
        view.addSubview(tableView)
        tableView.separatorStyle = .none
        tableView.delegate = self
        tableView.dataSource = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.register(cellType: CustomCell.self)
        
        sampleItems = [
            CustomCellItem(date: "2019/01/01", title: "あいうえお"),
            CustomCellItem(date: "2019/01/02", title: "かきくけこ"),
            CustomCellItem(date: "2019/01/03", title: "さしすせそ")
        ]
    }
}

私は基本的に、UITableViewのframe をCGRect.zeroで初期化して、左右上下の制約を親のViewに合わせる形で実装しています。 そして セルの登録 tableView.register(cellType: CustomCell.self) ですが、Extensionを書いて記述量を少なくしています。

public func register(cellType: UITableViewCell.Type, bundle: Bundle? = nil) {
    let className = String(describing: cellType)
    let nib = UINib(nibName: className, bundle: bundle)
    register(nib, forCellReuseIdentifier: className)
}

Extensionはこちら

こうしておくと、楽チンで良いですよ〜〜

カスタムCellの実装

カスタムCellは、Xibを利用して実装しています。

f:id:muchan611:20190824152312p:plain

Xibの Custom Class に紐付けるClass(今回は CustomCell)を指定したのちに、ラベルなどの各コンポーネントをCustomCellクラスに紐付けていきます。

UITableViewDataSource, UITableViewDelegateに準拠する

Protocolに準拠する場合、私はよく、見通しをよくするために 対象ViewControllerのextension に書くようにしています。

extension CustomCellListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sampleItems.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = sampleItems[indexPath.row]
        let cell = tableView.dequeueReusableCell(with: CustomCell.self, for: indexPath)
        cell.update(item: item)
        return cell
    }
}

ここの dequeueReusableCell(with: CustomCell.self, for: indexPath) も、UITableViewのExtensionを書いて記述量を少なくしています。

public func dequeueReusableCell<T: UITableViewCell>(with type: T.Type,
                                                    for indexPath: IndexPath) -> T {
    let className = String(describing: type)
    let cell = dequeueReusableCell(withIdentifier: className, for: indexPath) as? T
    guard let dequeueCell = cell else {
        fatalError("Could not dequeue a cell of class \(className)")
    }
    return dequeueCell
}

Extensionはこちら

そして、UITableViewDelegateはこんな感じ。

extension CustomCellListViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let viewController = UIViewController()
        viewController.view.backgroundColor = .white
        navigationController?.pushViewController(viewController, animated: true)
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

セルをタップした際は、次の画面に遷移するようにします。 戻ってきたときに、セルがグレー(選択状態)になってしまうのが格好悪いので tableView.deselectRow(at: indexPath, animated: true) で選択状態を解除します。

以上、わたしのUITableView実装テンプレートの説明でした。 こういった、自分なりのテンプレートを用意しておくと、新しい画面の実装スピードも早くなって、良いですね〜🤸🏼‍♂️✨

ではでは、残りの夏も楽しみましょ〜🍧
ウー早く北アルプス行きたい...!!⛰⛰

UIColor Extention と #colorLiteralで色指定を楽にする

やっと夏っぽくなってきて嬉しいです。そして天気の子は最高です。

今日はUIColor をExtentionする話。 自分のサービスでよく使う色などは、共通化してどこからでも使えるようにしたい。そんな時にはUIColorを拡張すると良いですよね。

view.backgroundColor = UIColor.myColor.backgroudGray

こんな感じで指定できて、便利です😃
で、その時に#colorLiteralを使うとめっちゃ分かりやすいのです。

#colorLiteralを使うと

f:id:muchan611:20190728175907p:plain

こんな感じで、色が一目瞭然👀 直感的ですね。 しかも、色の指定もGUI上でできて分かりやすい。

まず、colorLiteralはコード補完されます。

f:id:muchan611:20190728180602p:plain

色を変えたいときは、色自体をダブルクリックして、otherを選びます。

f:id:muchan611:20190728180315p:plain

すると色んな方法で色の指定ができるのです...!!便利だなぁ〜💓

f:id:muchan611:20190728180336p:plain

という訳で、皆さんもUIColor Extention と #colorLiteral で色の管理をしてみてはいかがでしょうか〜!

OAuthSwiftでtwitterログイン認証を実装する

iOStwitter認証を実装する際によく利用されていた、公式ライブラリであるTwitterKitがサポート終了していたので、代替方法としてOAuthSwifttwitterのログイン認証を実装した。その際の手順をメモ。

準備

実装

twitterログイン認証には、OAuth 1aを利用する必要がある。 OAuthSwiftにデモ実装の記述があるので、それをコピペすれば動いた。

OAuthSwiftのデモ実装のコードはこちら

最終的に私は以下のように実装。(ViewController内)

private lazy var oauthswift: OAuth1Swift = {
    return OAuth1Swift(
        consumerKey: "{ 自分のconsumerKeyを指定 }",
        consumerSecret: "{ 自分のconsumerSecretを指定 }",
        requestTokenUrl: "https://api.twitter.com/oauth/request_token",
        authorizeUrl: "https://api.twitter.com/oauth/authorize",
        accessTokenUrl: "https://api.twitter.com/oauth/access_token"
    )
}()

# 事前にXibにボタンを追加、タップされた時にログイン処理を行うように
@IBAction private func didTapTwitterLoginButton(_ sender: Any) {
    loginWithTwitter()
}

private func loginWithTwitter() {
    oauthswift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthswift)
    _ = oauthswift.authorize(
        withCallbackURL: URL(string: "oauth-swift://oauth-callback/twitter")!,
        success: { credential, response, parameters in
           # 成功した時の処理を記述
    },
        failure: { error in
           # 失敗した時の処理を記述
    }
    )
}

Xcodeで画像リソースを追加する

iOSアプリ内で表示する画像ファイルの追加手順です。
公式ドキュメント

手順

f:id:muchan611:20190714165958p:plain

使い方

let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "home")

# これでも読み込める
let image = UIImage(named: "home")

おまけ

TabBarやNavigationBarなどのアイコンに画像を設定する場合、公式ガイドラインにサイズ指定の記述があるので、それに合わせたサイズの画像を指定する

ルート画面に下タブを追加する

UITabBarController を利用することで、タブを表示することが可能。

前回の記事でrootViewControllerに指定した RootViewController ファイルを以下のように書き換えると、2タブの画面が表示される。 今回の例では、NavigationControllerもセットしています。

画面表示

f:id:muchan611:20190714162139p:plain:w300

コード

import UIKit

class RootViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let firstViewController = UIViewController()
        firstViewController.view.backgroundColor = .white
        let firstNavigationController = UINavigationController(rootViewController: firstViewController)
        firstNavigationController.tabBarItem = UITabBarItem(tabBarSystemItem: .favorites, tag: 1)

        let secondViewController = UIViewController()
        secondViewController.view.backgroundColor = .white
        let secondNavigationController = UINavigationController(rootViewController: secondViewController)
        secondNavigationController.tabBarItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 2)

        let viewControllers = [firstNavigationController, secondNavigationController]
        setViewControllers(viewControllers, animated: false)
    }
}

ちなみに、好きなアイコンを指定したい、という場合は、tabBarItemを以下のように指定する。

firstNavigationController.tabBarItem = UITabBarItem(title: "ホーム", image: UIImage(named: "home"), tag: 1)

上記の場合、home.pngを事前に追加しているが、画像追加手順については Xcodeで画像リソースを追加するに記述。