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

[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で続きを読む

【Swift5】ローカル通知から画面遷移を行う

背景

iOS10での通知周りの記事は多くあったが、古い記事になってしまったので、改めて実装を試してみた。
実装はStoryboardで行っています。

必要な手順

①通知の許可リクエスト
②SceneDelegateを使わない設定を行う
③適当に通知を飛ばす
④Storyboardで2画面目を追加してUserNitificationCenterのdelegateで遷移処理を実装

通知の許可リクエスト

AppDelegate.swift
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
            // Enable or disable features based on authorization.
        }
        return true
    }

// 省略
}

SceneDelegateを使わない設定を行う

こちらの記事を参考に使わない設定を行います。
https://qiita.com/u5_03/items/ff005e7cab7ff509f140

適当に通知を飛ばす

ViewController.swift
class ViewController: UIViewController,CLLocationManagerDelegate {

    var locationManager: CLLocationManager!

    override func viewDidLoad() {
        super.viewDidLoad()
        let content = UNMutableNotificationContent()
        content.title = "Hello!"
        content.body = "World!"
        content.sound = UNNotificationSound.default

        // 5秒後に発火する UNTimeIntervalNotificationTrigger 作成、
        let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5, repeats: false)

        // identifier, content, trigger から UNNotificationRequest 作成
        let request = UNNotificationRequest.init(identifier: "FiveSecondNotification", content: content, trigger: trigger)

        // UNUserNotificationCenter に request を追加
        let center = UNUserNotificationCenter.current()
        center.add(request)
    }
}

Storyboardで2画面目を追加してUserNitificationCenterのdelegateで遷移処理を実装

画面追加と遷移の処理ははこちらを参考にしてください!
https://qiita.com/tomu28/items/4bb327a8f80c042e41a1

AppDelegate.swift
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,UNUserNotificationCenterDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let center = UNUserNotificationCenter.current()
        // delegateを設定
        center.delegate = self

        center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
            // Enable or disable features based on authorization.
        }
        return true
    }

    // 通知をタップするとこのメソッドが呼ばれる
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        // Storyboardを指定
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        // Viewcontrollerを指定
        let initialViewController = storyboard.instantiateViewController(withIdentifier:"second")
        // rootViewControllerに入れる
        self.window?.rootViewController = initialViewController
        // 表示
        self.window?.makeKeyAndVisible()
    }


}

上記で通知タップ時に指定画面に遷移することが可能です、

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

[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で続きを読む

Swift学習。3週間、1日3時間程度やってみての進捗。

学習にあたって

筆者はアプリ開発に関して完全初心者なので、無難に本を買って進めることにしました。

使用した書籍は下記。

SwiftUI 徹底入門 | 金田 浩明 |本 | 通販 | Amazon
https://www.amazon.co.jp/dp/4815604061

得られた知見まとめ

本記事のタイトル通り、
3週間1日3時間程度やってみてSwiftに関して得られた知見をまとめさせていただきます。
※初学者なので単語が不適切なものがあるかもしれませんがご容赦ください。

データ構造(?)がわかりやすい

ひとつのswiftファイル(Swiftのプログラミングをするファイル)にひとつの画面が対応しているので、
どんなソースコードだとどんな画面になるのかが理解しやすい。

例えば下記のような画面のデータ構造にしたい場合は、Swiftファイルを3つ作ればよいことになります。

メモ帳リスト画面(メモを一覧で見る画面)
└食べ物についてのメモ画面
└LoLについてのメモ画面

やろうと思えばひとつのSwiftファイルでも作れそうですが、
現状の考えでは可読性を意識して、ひとつの画面にひとつのファイルを対応させえた方が良い気がしてます。

データ構造(?)がわかりやすい

データ構造(?)がわかりやすい

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