20200907のiOSに関する記事は8件です。

Error: Command PhaseScriptExecution failed with a nonzero exit code の対処方法

Carthage でライブラリをインストールしたり、アップデートしたりした後に、Branch をチャックアウトするとためにこのような Error が発生することがあります。このような場合は、Finder の情報を持つ拡張属性をし、Clean しビルドし直すと正常にビルドが成功するようになります。

解決方法

下記のコマンドを使ってプロジェクトルート以下の Finder 情報を持つ拡張属性を削除し、Xcode でクリーンを実行してからビルドすれば直ります。

sudo xattr -rc .
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI]TextFieldでFirstResponderでキーボードを非表示にする方法とtextFieldStyleによるサイズや色の変更方法

今回実装するもの

ダウンロード.gif

Code

ContentView
struct ContentView: View {
    @State private var text = ""
    @State private var flag = false
    var body: some View {
        let textFlag = String(flag)
        VStack {
            TextField("サンプル", text: $text)
            Text("\(textFlag)")
        } .textFieldStyle(CustomTextFieldStyle())
        .onTapGesture {
            if flag {
                UIApplication.shared.closeKeyboard()
            }
            flag.toggle()
        }
    }
}

struct CustomTextFieldStyle: TextFieldStyle {
    func _body(configuration: TextField<Self._Label>) -> some View {
        configuration
            .padding(.horizontal,16)
            .font(.system(size: 20))
            .frame(width: UIScreen.main.bounds.width, height: 60)
            .background(Color.secondary)
            .cornerRadius(8)
            .foregroundColor(.white)

    }
}

extension UIApplication {
    func closeKeyboard() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

解説

let textFlag = String(flag)によりキーボードの出し入れの様子を可視化しています。
またtapGestureによりUIApplication.shared.closeKeyboard()でキーボードを閉じています。
closeKeyboard()については

extension UIApplication {
    func closeKeyboard() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

とする事でキーボードを閉じる事ができます。

またSwiftUIではtextFieldStyleにてTextFieldを装飾する事ができます。
おすすめは

.frame(width: UIScreen.main.bounds.width, height: 60)

の部分でwidth: UIScreen.main.bounds.widthにする事でどのデバイスでも綺麗に表示する事ができます。

最後に

普段は個人でSwiftUIでアプリを作っています。ハードも作るの大好きです。
よければ下記もチェックして下さい♪
Twitter
https://twitter.com/oka_yuuji
note
https://note.com/oka_yuuji

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】ストライド型について

1.はじめに


今回は範囲型ともう一つの基本的なデータ型である、ストライド型について解説します。前回の記事では範囲型のことを解説しており、今回の記事とつながりがある内容なので、見てない方はお時間ありましたら是非ご覧ください。
URL(https://qiita.com/0901_yasyun/items/0e86c602ddff1c472786)

2.ストライド型とは


ストライド型とは、整数や実数などの比較できる値に対して、開始点と終了点、刻み幅から構成される構造体のことを指します。開始点から終了点まで、刻み幅ごとに値を取り出すことができます。
ストライド型には2種類あり、StrideThrough型とStrideTo型があります。それぞれの方のインスタンスは、標準ライブラリの次の関数を使って生成できます。型Tは実際に因数に指定して型によって決まります。

func stride(from: T, through: T, by: T.Stride) -> StrideThrough<T>
func stride(from: T, to: T, by: T.Stride) -> StrideTo<T>

3.StrideTo型


関数strideはfor-in文と一緒によく使われるので、その一例を以下に示します。

for x in stride(from:0, to:20, by:2){  // 0から20の直前まで2刻みで
    print(x, terminator:" ")
}

print()  // "0 2 4 6 8 10 12 14 16 18 "と表示する

このように、fromで指定した値からtoで指定した値まで、byで指定した値で刻みながらループしていることが分かります。しかしここで注目してほしいのは、最後の数が18であるという点です。このようにStrideTo型は、順番に取り出した最後の値が終了点の値と一致した場合、その値は採用しません。

4.StrideThrough型


一方、StrideThrough型は終了点の値も含みます。実数を使った例を以下に示します。

for x in stride(from:1.0, through:0.0, by:-0.2){  // 1.0から0.0のまで-0.2刻みで
    print(x, terminator:" ")
}

print()  // "1.0 0.8 0.6 0.4 0.2 0.0 "と表示する

このように、StrideThrough型をfor-in文で使った場合、終了点の値も含むことが分かります。ただし、刻み幅を使って増加、減少させていくので、必ずしも最後の値が終了点と同じになるとは限りません。また実数の場合には誤差が伴うので、加算していっても終了点の値と一致しないこともあります。

5.プロトコルStrideable型


ここでストライド型の引数の型について説明します。関数Stride(from: to: by:)の場合、宣言は正確に書くと以下のようになります。

func stride<T>(from:T, to:T, by:T.Stride) -> StrideTo<T>
    where T : Strideable

Tは型パラメータです。where T: Strideableの部分は、型TはプロトコルStrideable型に適合していなければいけないという制約を表しています。
プロトコルStrideable型にもきちんとした定義内容がありますが、若干主旨からずれてしまうのと、まだ私が記事で説明していない内容が出てくるなど、少し複雑な話になってしまうので、今回具体的な定義内容は省略します。
しかし、ストライド型を使ううえでプロトコルStrideable型が、見えないところで内部的に関わっているということだけ、頭の片隅に置いておきましょう。

6.おわりに


今回は前回に引き続き、範囲型ともう一つの基本的なデータ型であるストライド型について、簡単に解説しました。データ型は様々なコードを書く上で、効果的に使える場面が多々あると思います。経験を積むだけどんどん効果的に利用できるようになると思うので、積極的に使っていきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[アプリ開発]未経験がファッションアプリ作ってみた

bcaf1b5818b00452012ea3d246180192.gif

作った経緯

プログラミングスクールで勉強して半月ほど、、
私の京都に住んでいる祖母が
「コロナで近所の骨董品を扱っているお店が海外のお客さんが来れなくなってあまりよくない」と、
そこで私がちょうどアプリ開発を勉強していたのでオリジナルアプリを作る時にこれを制作してポートフォリオにしようと決めました。
スクールで学んだことを基本にそこから作ってみたいアプリを参考にして新しい技術を取り入れてやっていきました。
スクリーンショット 2020-09-06 13.04.15.png
サンプルで画像、ブランド名を拝借しております。

エラー

とにかくCollectionViewに手間取りました。
スクリーンショット 2020-09-06 13.11.28.png
基本手にTableViewと変わりませんが、ネットにあまり記事が出ていないので、、、
スクリーンショット 2020-09-06 13.11.06.png

結果

それから半月くらい、、
プロトタイプというか形になったので祖母にメールしました。
「こんな感じでどうかな。画像とかデータを商品名に変更したいから骨董品屋さんの電話番号教えて」
と送ると、
「そこは一流ばかりでそう易々と情報は無理だと思うよ。」
とまさかの返事。。。。

私は落胆しました、こんな頑張ったのにどうしてくれるんだと。
メンターと相談して、ポートフォリオは無駄じゃないしこのままリリースしようと決めました。

感想

アプリ開発は大変ですねw
高校でC言語を勉強して一応3年間やっていましたがあまり役に立たなかったですw
むしろ一からやっていたに近いかも。

このアプリにしようと思ったのは、これなら人のためと捗るし開発や運営などできるかなと思いましたが、
できなくて残念でした。
しかし、これに懲りずにガンガン開発していきます!
一人作業だし、自分でスケジューリングして、、ていうセルフワークでしたが、
できなかったところができるようになったり新しい技術が自分にできるようになると達成感がとても気持ちいいですね!!

参考にした記事

https://note.com/yujirosaito006/n/n42ca75b36931
https://i-app-tec.com/ios/collectionview-cellselect.html
https://qiita.com/NEKOKICHI2/items/1a42cb36c3aacbe50c70
https://qiita.com/Kawboy442/items/9fb8e8f14e0bc5ad7c1b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画面の管理と遷移

画面の管理と遷移パターン

画面の管理と遷移のパターンについて思ったことを書いていきます。
ポートフォリオを作成している途中で何回も画面の管理方法や遷移の仕方を変更したので、以下に調べたことをまとめておきます。
まず画面の管理のパターンは大きく分けると以下、3つになると思いました。
1.storyboardのみ
2.コードのみ
3.xib+コード

パターン1.storyboardのみ

■storyboardを使う場合の画面UI
→storyboardの機能を使ってUI全てを整える
storyboardはxibファイルの上位互換らしいので、storyboardを採用する場合はUIの設定はできる限りstoryboardの機能を使っていこうと思います。
ただ、cellの設定に関してはxibファイルを使おうと思います。storyboardだけだとすごく見にくい・・・。

■storyboardを使う場合の遷移
→Segueを使用
コードでは値を持って遷移させたいときは、pushとかは使わずにprepare for segueを使います。

Segueで値を持たせて遷移
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "hogeViewController" {
      let hogeVC: hogeViewController = (segue.destination as? hogeViewController)!
      hogeVC.url = url
    }
  }

パターン2.コードのみ

■コードを使う場合の画面UI
■コードを使う場合の遷移
→全てコード
とにかくコードを使います。画面の生成もコードです。

コードで画面の生成
class piyoViewController: UIViewController {
    let titleName: String

    init(titleName: String) {
        self.titleName = titleName
        super.init(nibName: nil, bundle: nil)
    }
}

コードだけで画面も遷移も行うのは、わたしにとってはまだまだレベルが高いです。。。
プロジェクトによってはコードだけのところもあるんでしょうか、、、

パターン3.xib+コード

■xib+コードを使う場合の画面UI
→xibを使って画面のUIを設定
UIの設定はxibを用います。
画面の生成はコード。

■xib+コードを使う場合の遷移
→コードで行います(pushとかで遷移)
なので、画面UI時にsegueは使いません。

push遷移
@IBAction func next(_ sender: Any) {
let nextVC = self.storyboard?.instantiateViewController(identifier: "nextVC") as! NextViewController
    navigationController?.pushViewController(nextVC, animated: true)
}

なんとなく、おいしいとこどりしたようなやり方だと思います。
storyboardを使用すると画面が多すぎて重くなることがあるそうなので(わたしの開発規模だと全然重くない)
バランスをとった方法だなと思います。
ただ、画面の生成をコードで行うのでコードの量がどうしても多くなります。

storyboardを使うか?

結論、storyboardをしようかするかどうかが肝だと思われます。
storyboard使うなら、使える機能は全部使うべきだなと思います。Xcodeの機能にも詳しくなれそうなのですし、管理するコード量も減らせるので。
ただし、画面が増えてくると画面のコンフリクトなどが起きるみたいなので徐々にコードで管理するようシフトしていきたいです。
プロジェクトによってどのぐらいの割合でコード管理がなされているかわかりかねますので、初学者のうちは素直にstoryboardを使って作成していこうと思います。

ただし、1storyboardに10以上の画面が存在する場合などは、機能ごとに1storyboardをわけると良さそうです。
他にも1画面1Storyboardといった管理方法もあるみたいです。試しに行ってみたところ全体の遷移がわかりづらかったので使うのをやめてしまいました。
もっと、複雑で大規模な画面管理を行うサービスとかを開発した時に使ってみたいです。

まとめ

画面の管理と遷移パターンまとめ
・Storyboardのみ(全画面を1Storyboardに乗せるとXcodeが重くなるので、機能別で分けたりする)
・コードのみ
・xib+コード(UIの構築はxibで行い、画面の生成や遷移はコードで行う)
遷移する時に使用するコード一覧や画面管理のスクリーンショットなどを追記していきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2020年版】AppStoreへの公開手順(ArchiveからAppStoreCponnectで公開)

はじめに

iOSって証明書や設定しなければならない部分が結構あって
慣れてしまえばただの作業なんですが、年々UI変わったりツールも変わったりするので

現在のipaファイル作成〜AppStoreConnectでの公開までをまとめました。(2020年3月時点)

参考サイトも載せますが、現在と若干違う部分があるので、
2020年で自分の場合こんな感じっていう例でスクショ入れつつ面倒かった部分を載せていきます。

自分なりに調べてやりましたが、分かりにくいところがあるかもしれません。

いろいろコメントなどで教えていただければ幸いです。

手順

1. Appleアカウント作成、Apple Developer Programに登録

 iOSアプリ公開やPush通知するためには、有料アカウントにする必要があります。

 税込み1万3000円くらい。

 企業用アカウントでは申請できないので個人アカウント作成で。
 登録して承認されるまで2日くらいかかるらしいんですが、自分は電話したら速攻承認してくれました。

 すぐやりたい方は電話とか問い合わせしてみては。

2. Certificate作成

 証明書です。どのDevelopperですかみたいなやつ。

 作り方に関しては探せば出てきます。

 若干記事と違うのはXcode11以降からはApple Distributionを選択すること。

 昔のやつはiOS Distribution (App Store and Ad Hoc)っすね。

 申請というかArchiveするにはDistribution用が必要です。

 開発する際はDevで。

こんな感じ。

20200726201633.png

 

3. AppID作成、Provisioningfile作成

すでに開発してあればあるはずですが一応。
 

AppIDはIdentiferタブから作成です。

 詳細は省きますが画像だけ。

 
20200726201639.png

追加されるとNameの横に本当は決めたbundleIDが表示されてます。

20200726201642.png

ProvisioningfileはProfileタブから。

こちらも詳細は省きます。

申請の場合はAppStoreを選択です。

20200726201646.png



自分の場合は1アプリに対してDev,Adhoc,AppStore用を作成。

EnterPrise版ははそれ用のアカウントで。

20200726201651.png


4. Device追加(必要な場合)

AddHocで配信する場合にここで登録した端末でしかインストールできないので使いたい場合は追加します。

TestFlight使う人とかAdhoc配信しかしてない現場とかはやります。

登録上限もあって一年に一回しか削除できない(厳密にいうといつでも削除はできるがゴミ箱にいる状態)ので気を付ける必要もあります。

EnterPriseでDeploygate配信するとかなら端末制限ないので気にせずとも。

5. AppStoreConnectにアプリ登録

調べると出てくるので省きまっす。
バンドルIDとか洗濯して名前登録するやーつ。

6. App情報編集

名前、サブタイトル、プライバシーポリシーURL、カテゴリを設定して、「保存」。

プライバシーポリシーは今年くらいから必須になりましたので注意。

プライバシーポリシー作成はGithubにhtmlファイル置いてやるやり方が主流だと思いますが

自分はApp Privacy Policy Generator
というサイトでやってます。
余裕があれば詳しく書きます。

20200726201655.png


7. バージョン情報編集

バージョン情報の編集は主にAppStoreで公開されたアプリページに載せる画像や説明文などを追加します。

画像は縦か横を選択できますが、トレンド的には横です。(横の方がインストール率が高い。)

枚数は、3枚からあまりインストール率に関して大差ないデータがありました。
なので特になければ3枚あれば十分かも。

ツールはPixel Materを使いました。
Appアイコンとかデザインはこれでやりました。

AdobePhotoShopまではいらない人とかにおすすめです。ここら辺は自分で好きなのを。

20200726201704.png


8. アーカイブ作成&アップロード

こちらは今度詳しく書きます。
「ipa作成」,「xcode archive」とかで検索すれば出ていきます。

アップロードはXcodeそのままやるか、Transporter使って上げるかです。
昔はApplicationLoaderとかでした。

9. アプリを審査提出、公開

先ほどのバージョン情報を編集するところでアップロードしたアプリを選択して、審査提出をします。

公開のタイミングは3種類あります。
1. 審査が通った瞬間自動で
2. 審査認可後、手動で好きなタイミングで。
3. 審査認可後、予約したタイミングで出す。

です。

リジェクトされなければ1〜2日で許可でした。
ネットとかだと2~3日が多いですが、昔に比べるとかなり早くなってきてるようです。

最後に

長いし見辛いところもあると思いますが、
気になる部分だけ参考にしていただければと思います。
雑な部分はあとで別記事とかに書ければ書きます。

手探りしながらの申請だったので、もちろん

「私、俺ならこうやるよ」

っていうのがあればご教示いただければ幸いですm(._.)m

参考サイト:
【2019年版】iOSアプリをApp Storeに公開するための全手順まとめ
個人開発者がAppStore用にプライバシーポリシーを書く

20200726201712.png
20200726201721.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS/Swift] カスタムデザインのローカル通知の実装方法

こんなやつ

通常のローカル通知ではなく、オリジナルなデザインのローカル通知を出したい!!

ステップ

  1. バナーを表示する
  2. バナーを隠す
  3. ジェスチャーを追加する
  4. タイマーをセットする
  5. 使いやすいように修正

ソースだけ見たい人はこちら

1. バナーを表示する

下準備

今回の主役InAppNotificationViewを用意する

class InAppNotificationView: UIView {
    // 1
    lazy var containerView: UIView = {
        let view = UIView()
        view.alpha = 0
        return view
    }()
    // 2
    lazy var messageLabel: UILabel = {
        let label = UILabel()
        label.text = "This is In App Notification!!"
        label.textAlignment = .center
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 20)
        label.backgroundColor = .systemBlue
        label.layer.cornerRadius = 10
        label.clipsToBounds = true
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    // 3
    override init(frame: CGRect) {
        super.init(frame: frame)

        addSubview(containerView)

        containerView.addSubview(messageLabel)
        let constraints = [
            messageLabel.topAnchor.constraint(equalTo: containerView.topAnchor),
            messageLabel.leftAnchor.constraint(equalTo: containerView.leftAnchor),
            messageLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            messageLabel.rightAnchor.constraint(equalTo: containerView.rightAnchor)
        ]
        NSLayoutConstraint.activate(constraints)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  1. 表示したいviewを入れるコンテナviewを用意する。alpha=0で透明にしておく
  2. 今回は簡単なメッセージを表示するだけなので、コンテナの中身は単純なUILabel
  3. 表示したいviewをコンテナに入れ、コンテナを自身にaddSubview()で追加する。この時、中身のレイアウトは設定するが、コンテナのレイアウト(frame)は決めない

バナー表示

バナーを表示するメソッドを用意する。基本的に自身のframeは一度決めたら変更はせず、その上にのっけたコンテナviewのframeを操作してバナーの出し入れを表現することに注意。

var targetWindow: UIWindow? {
    UIApplication.shared.connectedScenes
        .first { $0.activationState == .foregroundActive }
        .map { $0 as? UIWindowScene }
        .flatMap { $0 }?
        .windows.first
}

func showBanner() {
    // 1
    guard let window = targetWindow else { return }
    // 2
    let width = window.frame.width
    let height = bannerHeight + window.safeAreaInsets.top
    frame = CGRect(x: 0, y: 0, width: width, height: height)
    // 3
    containerView.frame = CGRect(
        x: bannerMargin + window.safeAreaInsets.left,
        y: height - bannerHeight,
        width: width - ((bannerMargin * 2) + window.safeAreaInsets.left + window.safeAreaInsets.right),
        height: bannerHeight
    )
    containerView.setNeedsLayout()
    containerView.layoutIfNeeded()
    // 4
    targetWindow?.addSubview(self)
    containerView.alpha = 0
    containerView.transform = CGAffineTransform(translationX: 0, y: -frame.height)
    // 5
    UIView.animate(withDuration: 0.5, animations: {
        self.containerView.alpha = 1
        self.containerView.transform = .identity
    }, completion: nil)
}
  1. バナーを乗っけるviewを取得する。UIWindowを取得するのは、バナーを画面の一番上から表示させるため。言い換えると、statusBarの上から重なる様に表示させるため
  2. 自身(InAppNotificationView)のframeを設定する
  3. コンテナのframeを設定する。frameの変更を即座に反映するために setNeedsLayout()layoutIfNeeded()を実行する
  4. windowに自身をaddSubview()する。この時alpha=0で透明にすると共に、コンテナviewを画面の外に隠れる様にtransformしておく
  5. alpha=1で透明にしていたコンテナを表示させつつ、transform = .identityで(4)で行った変形(コンテナの移動)を元に戻す処理をアニメーションさせながら実行する

ここまでを確認する場合

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        // view controllerが表示されたら処理スタート!
        InAppNotificationView().showBanner()
    }
}

バナーが表示されればOK!!

2. バナーを隠す

(1)とは逆の処理

func closeBanner() {
    UIView.animate(withDuration: 0.5, animations: {
        self.containerView.alpha = 0
        self.containerView.transform = .init(translationX: 0, y: -self.frame.height)
    }, completion: { _ in
        print("on Closed!!")
        self.removeFromSuperview()
    })
}

バナーを表示させた時とは反対に、またコンテナを透明にしつつ、画面の上に隠す様にアニメーションさせる。完了したら、自身を親viewから削除する

基本的にInAppNotificationViewは画面上部に出しっぱなしで、その上にのせたコンテナviewを表示したり隠したりするイメージ。

3. ジェスチャーを追加する

バナーをタップした時

lazy var tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapped))

@objc func didTapped(_ sender: UITapGestureRecognizer) {
    closeBanner()
    print("on Tapped!!")
}

バナーをパンした時

lazy var panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPanned))

var currentPositionY: CGFloat = 0

@objc func didPanned(_ sender: UIPanGestureRecognizer) {
    switch sender.state {
    case .began:
        break
    case .changed:
        // 1
        let point = sender.translation(in: self)   
        // 2
        guard currentPositionY + point.y <= 0 else { return }
        // 3
        currentPositionY += point.y
        containerView.transform = .init(translationX: 0, y: currentPositionY)
        // 4
        sender.setTranslation(.zero, in: containerView)
    case .ended, .cancelled:
        // 5
        if (abs(currentPositionY) > bannerHeight / 2) {
            closeBanner()
        } else {
            // 6
            UIView.animate(withDuration: 0.2) {
                self.containerView.transform = .identity
                self.currentPositionY = 0
            }
        }
    default:
        break
    }
}

panジェスチャーを行っている途中

  1. sender.translation(in: self)で、panされた移動量が取得できる
  2. 下方向へのpanは無視する
  3. panした移動量の分だけコンテナを変形(移動)させる
  4. ここでリセットすることによって、(2)でまたpanした移動量を取得できる

panジェスチャーが終わった、又はキャンセルした時

  1. panジェスチャーによって一定以上、バナーが画面上部に移動していた場合、そのままバナーを閉じる。(abs()は数値の絶対値を取得する。)
  2. そうでない時は、バナーを元の位置に戻し、移動量も0にリセットする

4. タイマーをセットする

// 1
var bannerClosingTimer: Timer? {
    didSet {
        oldValue?.invalidate()
    }
}
// 2
func countDownToCloseBanner() {
    bannerClosingTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] _ in
        self?.closeBanner()
    }
}
// 3
func showBanner() {
    // ~ ~ ~ ~
    countDownToCloseBanner()
}
// 4
case .ended, .cancelled:
    if (abs(currentPositionY) > bannerHeight / 2) {
        closeBanner()
    } else {
        // ~ ~ ~ ~
        countDownToCloseBanner()
    }
// 5
case .began:
    bannerClosingTimer = nil

func closeBanner() {
    bannerClosingTimer = nil
    // ~ ~ ~ ~
}
  1. Timerを保持するプロパティを宣言。新しいTimerがセットされた時、メモリリークを防ぐために古いTimerをinvalidate()で無効にする
  2. タイマーをスタートさせるメソッド。今回は5秒経過するとバナーを閉じる

タイマーをスタートするのは
3. バナーを表示させた時
4. 閉じかけたバナーが元の位置に戻った時

タイマーをキャンセルするのは
5. panジェスチャーが開始された時
6. バナーを閉じる時

5. 使いやすいように修正

処理やデータをまとめる

struct InAppNotification {
    let message: String
    let onTap: (() -> Void)?
    let onClosed: (() -> Void)?
}

let notification: InAppNotification

init(notification: InAppNotification) {
    self.notification = notification
    // ~ ~ ~ ~
}

アプリ内通知に必要なデータ(or処理)をまとめたstructを用意し、InAppNotificationViewに渡す。
あとは適宜、必要なデータ(or処理)を必要な箇所で呼ぶ。
(具体的な箇所は最後に貼ったリンクを参照)

Protocolにまとめる

protocol InAppNotificationShowable {
    func showInAppNotification(_ notification: InAppNotification)
}

extension InAppNotificationShowable where Self: UIViewController {
    func showInAppNotification(_ notification: InAppNotification) {
        InAppNotificationView(notification: notification).showBanner()
    }
}

使い方

// InAppNotificationShowableに準拠したUIViewController内で
let notification = InAppNotification(
    message: "This is In App Notification!!",
    onTap: { print("on tapped!!") },
    onClosed: { print("on closed!!") }
)
self.showInAppNotification(notification)

完成!

最後に

コード全てを確認したい方はこちら!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift で map, compactMap, flatMap を使いこなそう

今回は初学者向けに、Swift からプログラミング言語を学び始めた人が必ずいだく mapcompactMapflatMap の違いについて簡単に解説をしていきたいと思います。また、これらの関数は積極的に使っていくことで状況に応じた使い分けなどがうまくできるようになっていくので積極的に使っていきましょう!

map

map は主に配列のデータを別の配列データに変換したい時などに使用する関数です。たとえば、整数の配列を文字列の配列として変換する時のサンプルコードは下記のようになります。$0 でクロージャでの引数を参照することができます。クロージャーが分からない方はこちらの記事を見ていただければ把握できるかと思います。

let numberList: [Int] = [1, 2, 3, 4, 5, 6]

let numberStringList: [String] = numberList.map { "\($0)" }

print(numberStringList) // ["1", "2", "3", "4", "5", "6"]

compactMap

compactMap は配列のデータから nil を取り除きたい場合に使用する関数です。nil を含む配列から nil を取り除いた配列を取得するサンプルは次のようになります。

let optionalNumberList: [Int?] = [1, 2, nil, 4, 5, nil]

let numberList: [Int] = optionalNumberList.compactMap { $0 }

print(numberList) // [1, 2, 4, 5]

また、compactMap は配列で nil を取り除いてくれるので、配列のデータを使用して nil になる可能性のある別の型のデータ配列に変換する時などによく使用されます。また、偶数値を渡された時のみ初期化できる Event を整数値の配列から nil の値を取り出すして配列に変換するサンプルコードは下記になります。

struct Even {
    let evenString: String

    init?(number: Int) {
        if number % 2 == 0 {
            evenString = "\(number)"
        } else {
            return nil
        }
    }
}

let numberList: [Int] = [1, 2, 3, 4, 5, 6]

let eventList: [Even] = numberList.compactMap { Even(number: $0) }

print(eventList.map { $0.evenString }) // ["2", "4", "6"]

flatMap

flatMap はクロージャー内で返された配列たちを(多次元配列)一次元配列に変換する際に利用します。サンプルコードとして、理解しやすいように map の結果と比較しています。

let numberList: [[Int]] = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]

let numberStringListWithFlatMap: [String] = numberList.flatMap { $0.map { "\($0)"} }
let numberStringListWithMap: [[String]] = numberList.map { $0.map { "\($0)" }}

print("flatMap: \(numberStringListWithFlatMap)") // flatMap: ["1", "2", "3", "1", "2", "3", "1", "2", "3"]
print("map: \(numberStringListWithMap)") // map: [["1", "2", "3"], ["1", "2", "3"], ["1", "2", "3"]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む