20201112のSwiftに関する記事は14件です。

TableViewのヘッダーではなくNavigationItemのTitleだった件

iOS標準のメモアプリやメッセージアプリなど
TableViewのHeaderViewっぽいのがスクロールするとNavigationItemのTitleに表示される動きについて、TableViewのHeaderViewを触るものと思い込んでしまったせいでハマったので覚書き。
NavigationTitle.gif

実装手順

  • NavigationBarのPrefers Large Titlesにチェックを入れる
  • NavigationItemのTitleにタイトルを入力
  • NavigationItemのLargeTitleをAutomaticに。

以上!!
一瞬でした。

参考記事
https://qiita.com/mcz9mm/items/cd005814550087c7c707

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

[iOS]ランダムな座標にUIImageを生成する

普段Storyboardを使ってUIを構築している私ですが、ランダムな座標にUIImageを生成しようと思って調べたら色々勉強になったのでまとめます。

バージョン

Swift 5.3
Xcode 12.1

この記事で述べること

  • storyboardを使わずにUIを画面に描画する方法

画面の真ん中に画像を配置する

とりあえず、コードから決まった位置に画像を配置します。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // UIImageの初期化
        let image: UIImage = UIImage(named: "ninja")!

        // UIImageViewの初期化
        let imageView = UIImageView(image: image)

        // 画面の縦幅・横幅を取得
        let viewWidth: CGFloat = view.frame.size.width
        let viewHeight: CGFloat = view.frame.size.height

        // 画像に設定したい縦・横幅を変数に格納
        let imageWidth: CGFloat = 100
        let imageHeight: CGFloat = 100

        // 画像の座標・大きさを生成
        let rect: CGRect = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)

        // 指定した座標・大きさを設定
        imageView.frame = rect;

        // 画像を画面の中央を指定
        imageView.center = CGPoint(x: viewWidth / 2, y: viewHeight / 2)

        // viewにUIImageViewを追加
        self.view.addSubview(imageView) 

    }

}

コードだけで画像が真ん中に配置されました。かわいいね。

ランダムな座標に画像を生成する

CGPointの座標はCGFloatを引数に取るので、

CGPoint(x: CGFloat.random(in: 0...viewWidth), y: CGFloat.random(in: 0...viewHeight))

でランダムな座標を生成することが出来る。

なので、上の画面の真ん中に画像を生成するコードの

imageView.center = CGPoint(x: viewWidth / 2, y: viewHeight / 2)

の部分を

imageView.center = CGPoint(x: CGFloat.random(in: 0...viewWidth), y: CGFloat.random(in: 0...viewHeight))

にすることでランダムな座標で画像が生成される。

まず、今はViewDidLoadの中に画像生成のコードを書いているので、関数にまとめます。

override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func generateUIIMageVIew() -> Void {
        // UIImageの初期化
        let image: UIImage = UIImage(named: "ninja")!

        // UIImageViewの初期化
        let imageView = UIImageView(image: image)

        // 画面の縦幅・横幅を取得
        let viewWidth: CGFloat = view.frame.size.width
        let viewHeight: CGFloat = view.frame.size.height

        // 画像に設定したい縦・横幅を変数に格納
        let imageWidth: CGFloat = 100
        let imageHeight: CGFloat = 100

        // 画像の座標・大きさを生成
        let rect: CGRect = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)

        // 指定した座標・大きさを設定
        imageView.frame = rect;

        // 画像を画面の中央を指定
        imageView.center = CGPoint(x: CGFloat.random(in: 0...viewWidth), y: CGFloat.random(in: 0...viewHeight))

        // viewにUIImageViewを追加
        self.view.addSubview(imageView)
    }

ボタンを付けます


そしてボタンと関連付けます。

Untitled.png

そうするとこんな感じでボタンを押すたびに画像がランダムな位置で生成されました。すごいね

一定の時間で画像が生成されるようにする

Timerを使って1秒ごとに画像が生成されるようにします。

  1. ボタンを削除して@IBActionの部分を@objcにする。
  2. タイマーの初期化

で実装できます。

    var generateImageTimer = Timer()
    override func viewDidLoad() {
        super.viewDidLoad()
        generateImageTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateUIIMageView), userInfo: nil, repeats: true)
    }

    @objc func generateUIIMageView() -> Void {
        // UIImageの初期化
        let image: UIImage = UIImage(named: "ninja")!

        // UIImageViewの初期化
        let imageView = UIImageView(image: image)

        // 画面の縦幅・横幅を取得
        let viewWidth: CGFloat = view.frame.size.width
        let viewHeight: CGFloat = view.frame.size.height

        // 画像に設定したい縦・横幅を変数に格納
        let imageWidth: CGFloat = 100
        let imageHeight: CGFloat = 100

        // 画像の座標・大きさを生成
        let rect: CGRect = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)

        // 指定した座標・大きさを設定
        imageView.frame = rect;

        // 画像を画面の中央を指定
        imageView.center = CGPoint(x: CGFloat.random(in: 0...viewWidth), y: CGFloat.random(in: 0...viewHeight))

        // viewにUIImageViewを追加
        self.view.addSubview(imageView)
    }

自動で勝手に画像が生成されるようになりました。すばらしい!

参考文献

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

Swift 5.3のコードをObjective-Cから呼び出す方法

Apple Silicon第一弾のM1チップ搭載のMacがついに登場しましたね。ネット上では賛否両論が渦巻いている感じです。

私は現在Macユーザーですが,自分のMacのリプレースというよりは,私が進めている人工衛星プロジェクトへの応用の可能性に注目しています。新しいMacの価格あたりの画像処理性能が極めて高く,消費電力あたりの画像処理性能も良好なのではないかと期待しています。人工衛星プロジェクトについては,Elixir Advent Calendar 2020で書く予定なので,どうぞ楽しみにしてください。

で,iOSアプリやMacアプリを開発するつもりではなく,ElixirからNIFというCプログラムを呼出す仕組みを利用して,ElixirからCore ImageやCore MLなど,Appleが蓄積しているSwiftのコード資産を呼び出すことができないか,という目的で,この記事を書きました。Objective-CからSwiftを呼び出せれば,CからObjective-Cを呼び出すことはできるので,ElixirからSwiftを呼び出すことも可能になります。

Swiftについては,Apple Developer Programを除き,古い言語仕様での記事しかなかったので,2020.11.12現在最新のXcodeであるXcode 12.1で採用されているSwift 5.3に対応している本記事は,類似する記事に対して新規性があるのではないかと思います。

また,Xcodeではなく,コマンドラインでmakeをつかってSwift/Objective-Cコードをコンパイルしています。Makefileの書き方も参考になるのではないかと思います。

出来上がったコード

さっそくコードを共有します。Apache-2.0 LicenseでGitHubに公開しています。

https://github.com/zacky1972/swift_objc_test

Swiftコード(callee)

呼び出したいSwiftコードは次のとおりです。

ExampleClass.swift
import Foundation

@objc class ExampleClass: NSObject {
    var count = 0
    @objc func increment() {
        count += 1
        NSLog("increment")
    }
    @objc func increment(by amount: Int) {
        count += amount
    }
    @objc func reset() {
        count = 0
    }
}

このコードの出典はこちらです。https://docs.swift.org/swift-book/LanguageGuide/Methods.html

Objective-Cコード(caller)

Objective-Cから呼び出せるようにするためには,次の2つのことを行います。

  • import Foundationとして,NSObjectから派生するようにクラスを定義する
  • @objcをクラスと,Objective-Cから呼び出したいメソッドに付記する

Objective-C側のコードはこんな感じです。

main.m
#import <Foundation/Foundation.h>
#import "ExampleClass-Swift.h"

int main(int argc, char** argv)
{
    ExampleClass *obj = [[ExampleClass alloc] init];
    [obj increment];
    NSLog(@"Testing");
}

ポイントは次のとおりです。

  • クラス名がExampleClassである場合には,import "ExampleClass-Swift.h"とする(クラス名に-Swift.hをつけたヘッダファイルをインポートする)

あとは普通にObjective-Cに読み替えて呼び出すだけです。

実行結果

実行結果は次のとおりです。

% ./hello 
2020-11-12 18:47:57.204 hello[10554:225821] increment
2020-11-12 18:47:57.204 hello[10554:225821] Testing

期待したとおり、さきにincrementメソッドが呼ばれてから,Testingと表示されています。

準備するコードは,たったこれだけです。

Makefile

ではどのようにビルドするか,Makefileを見ていきましょう。

Makefile
.phony: all clean

all: hello


main.o: main.m ExampleClass-Swift.h
    clang -c $< -o $@

ExampleClass.o: ExampleClass.swift ExampleClass-Swift.h
    swiftc -emit-object -parse-as-library $<

ExampleClass-Swift.h: ExampleClass.swift
    swiftc $< -emit-objc-header -emit-objc-header-path $@

hello: main.o ExampleClass.o
    swiftc $^ -o $@ -framework Foundation

clean:
    $(RM) hello *.o *.{swiftdoc,swiftmodule,swiftsourceinfo} ExampleClass ExampleClass-Swift.h

ポイントは次のとおりです。

  • Swiftのコンパイルにはswiftcを用います。-emit-object-parse-as-libraryオプションをつけることで,オブジェクトファイル(.o)へとコンパイルします
  • Objective-Cのコンパイルにはclangを用います
  • SwiftとObjective-Cを含むオブジェクトファイルをリンクするには,swiftcを用い,リンカオプションとして-framework Foundationを指定します
  • [ここが最大のポイント] ExampleClass-Swift.h(Objective-Cに与えるヘッダファイル)を生成するには,swiftcを用いて,-emit-objc-headerオプションと-emit-objc-header-pathオプションを用います。-emit-objc-header-pathオプションの直後に生成したいヘッダファイルへのパスを指定します

生成されたObjective-Cヘッダファイル

生成されたObjective-Cヘッダファイルは次のとおりです。

ExampleClass-Swift.h
// Generated by Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
#ifndef EXAMPLECLASS_SWIFT_H
#define EXAMPLECLASS_SWIFT_H
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgcc-compat"

#if !defined(__has_include)
# define __has_include(x) 0
#endif
#if !defined(__has_attribute)
# define __has_attribute(x) 0
#endif
#if !defined(__has_feature)
# define __has_feature(x) 0
#endif
#if !defined(__has_warning)
# define __has_warning(x) 0
#endif

#if __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif

#pragma clang diagnostic ignored "-Wauto-import"
#include <Foundation/Foundation.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#if !defined(SWIFT_TYPEDEFS)
# define SWIFT_TYPEDEFS 1
# if __has_include(<uchar.h>)
#  include <uchar.h>
# elif !defined(__cplusplus)
typedef uint_least16_t char16_t;
typedef uint_least32_t char32_t;
# endif
typedef float swift_float2  __attribute__((__ext_vector_type__(2)));
typedef float swift_float3  __attribute__((__ext_vector_type__(3)));
typedef float swift_float4  __attribute__((__ext_vector_type__(4)));
typedef double swift_double2  __attribute__((__ext_vector_type__(2)));
typedef double swift_double3  __attribute__((__ext_vector_type__(3)));
typedef double swift_double4  __attribute__((__ext_vector_type__(4)));
typedef int swift_int2  __attribute__((__ext_vector_type__(2)));
typedef int swift_int3  __attribute__((__ext_vector_type__(3)));
typedef int swift_int4  __attribute__((__ext_vector_type__(4)));
typedef unsigned int swift_uint2  __attribute__((__ext_vector_type__(2)));
typedef unsigned int swift_uint3  __attribute__((__ext_vector_type__(3)));
typedef unsigned int swift_uint4  __attribute__((__ext_vector_type__(4)));
#endif

#if !defined(SWIFT_PASTE)
# define SWIFT_PASTE_HELPER(x, y) x##y
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
#endif
#if !defined(SWIFT_METATYPE)
# define SWIFT_METATYPE(X) Class
#endif
#if !defined(SWIFT_CLASS_PROPERTY)
# if __has_feature(objc_class_property)
#  define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
# else
#  define SWIFT_CLASS_PROPERTY(...)
# endif
#endif

#if __has_attribute(objc_runtime_name)
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
#else
# define SWIFT_RUNTIME_NAME(X)
#endif
#if __has_attribute(swift_name)
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
#else
# define SWIFT_COMPILE_NAME(X)
#endif
#if __has_attribute(objc_method_family)
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
#else
# define SWIFT_METHOD_FAMILY(X)
#endif
#if __has_attribute(noescape)
# define SWIFT_NOESCAPE __attribute__((noescape))
#else
# define SWIFT_NOESCAPE
#endif
#if __has_attribute(ns_consumed)
# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
#else
# define SWIFT_RELEASES_ARGUMENT
#endif
#if __has_attribute(warn_unused_result)
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#else
# define SWIFT_WARN_UNUSED_RESULT
#endif
#if __has_attribute(noreturn)
# define SWIFT_NORETURN __attribute__((noreturn))
#else
# define SWIFT_NORETURN
#endif
#if !defined(SWIFT_CLASS_EXTRA)
# define SWIFT_CLASS_EXTRA
#endif
#if !defined(SWIFT_PROTOCOL_EXTRA)
# define SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_ENUM_EXTRA)
# define SWIFT_ENUM_EXTRA
#endif
#if !defined(SWIFT_CLASS)
# if __has_attribute(objc_subclassing_restricted)
#  define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
#  define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# else
#  define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
#  define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# endif
#endif
#if !defined(SWIFT_RESILIENT_CLASS)
# if __has_attribute(objc_class_stub)
#  define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub))
#  define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME)
# else
#  define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME)
#  define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
# endif
#endif

#if !defined(SWIFT_PROTOCOL)
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
#endif

#if !defined(SWIFT_EXTENSION)
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
#endif

#if !defined(OBJC_DESIGNATED_INITIALIZER)
# if __has_attribute(objc_designated_initializer)
#  define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
# else
#  define OBJC_DESIGNATED_INITIALIZER
# endif
#endif
#if !defined(SWIFT_ENUM_ATTR)
# if defined(__has_attribute) && __has_attribute(enum_extensibility)
#  define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
# else
#  define SWIFT_ENUM_ATTR(_extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM)
# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# if __has_feature(generalized_swift_name)
#  define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# else
#  define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
#endif
#if !defined(SWIFT_UNAVAILABLE)
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
#endif
#if !defined(SWIFT_UNAVAILABLE_MSG)
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
#endif
#if !defined(SWIFT_AVAILABILITY)
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
#endif
#if !defined(SWIFT_WEAK_IMPORT)
# define SWIFT_WEAK_IMPORT __attribute__((weak_import))
#endif
#if !defined(SWIFT_DEPRECATED)
# define SWIFT_DEPRECATED __attribute__((deprecated))
#endif
#if !defined(SWIFT_DEPRECATED_MSG)
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
#endif
#if __has_feature(attribute_diagnose_if_objc)
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
#else
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
#endif
#if !defined(IBSegueAction)
# define IBSegueAction
#endif
#if __has_feature(modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
@import ObjectiveC;
#endif

#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"

#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="ExampleClass",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif


SWIFT_CLASS("_TtC12ExampleClass12ExampleClass")
@interface ExampleClass : NSObject
- (void)increment;
- (void)incrementBy:(NSInteger)amount;
- (void)reset;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#pragma clang diagnostic pop
#endif

まとめ

この知見により,Swift 5.3のコードをObjective-Cから呼び出すことができるようになりました。別途CからObjective-Cを呼び出すコードを書くことで,CからSwiftを呼び出すことができるようになります。すなわち,ElixirからSwiftやAppleのAPIが自在に呼び出せるということです。万歳!

謝辞

本研究成果は、科学技術振興機構研究成果展開事業研究成果最適展開支援プログラム A-STEP トライアウト JPMJTM20H1 の支援を受けた。

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

【Swift】これで解決!Delegate実装の図解

はじめに

Swiftを始めた人なら誰しもが最初に思ったであろう
教材・記事「Delegateは代理人で..」
読者「代理人ってなんやねん」

この記事では図解でDelegateを紹介し、使い方まで説明します。
読み終えた頃には
「なるほどDelegateは代理人だな」
と思っていることでしょう。

Delegateを理解する

スクリーンショット 2020-09-29 21.32.32.png
以下、protocol PopViewDelegate: UIViewの部分が間違っていて
   class PopupView: UIView になります。

スクリーンショット 2020-09-29 21.36.38.png

終わりに

いかがでしたでしょうか?
「Delegateは代理人なんだな」と思っていただけたことかと思います。
周りにDelegateの理解に苦しんでいる方がいたら是非、
この記事を紹介してみてください。

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

【SwiftUI】Combineを使った入力フォームのバリデーションチェック

Combineを使ってTextFieldの入力のバリデーションチェック

入力フォームのバリデーションチェックを、Combineで実行します。

開発環境

  • macOS 11
  • Xcode 12 beta

View側の実装

全体のソースコード。

struct CreateUserView: View {

    @StateObject var vm = CreateUserViewModel()

    var body: some View {
        NavigationView {
            Form {
                TextField("Username", text: $vm.username)
                TextField("Password", text: $vm.password)
                TextField("Password Again", text: $vm.passwordAgain)
                Button(action: {}, label: {
                    Text("Submit")
                })
                .disabled(!vm.readyToCreate)

                Section(header: Text("DEBUG")) {
                    Text(vm.readyToCreate ? "Ready to create" : "Waiting input")
                }
            }
            .navigationTitle("Create User")
        }
    }
}

Formでリストを構築し、ユーザー名のTextFieldと、パスワードと二回目パスワードのSecureFieldを入れる。

入力された情報は、ViewModelに持たせて、そこでバリデーションチェックを行う。
readyToCreateは、一回目パスワードと二回目パスワードが一致する、かつユーザー名の空ではないときにtrueになる。

Submitボタンは、バリデーションチェック結果のreadyToCreateTrueの場合のみ活性化になる。

デバッグのため、下にreadyToCreateの値を表示する。

※Xcode 12 RC/iOS14.2 でSecureFieldがバグっていて上手く表示されないこともある。その時はTextFieldに変えてください

ViewModel側の実装

全体のソースコード。

class CreateUserViewModel: ObservableObject {
    @Published var username = ""
    @Published var password = ""
    @Published var passwordAgain = ""
    @Published var readyToCreate = false

    private var anyCancellable = Set<AnyCancellable>()

    init() {
        $username
            .combineLatest($password, $passwordAgain)
            .map {
                let username = $0.0
                let password = $0.1
                let passwordAgain = $0.2

                guard username.count > 0, password.count > 0, passwordAgain.count > 0 else { return false }
                guard password == passwordAgain else { return false }
                return true
            }
            .assign(to: \.readyToCreate, on: self)
            .store(in: &anyCancellable)
    }
}

①ストリームを合成する

$username
    .combineLatest($password, $passwordAgain)

combineLatestを使って$username$password$passwordAgainの3つのストリームを結合させる。
どちらかが新しい値が出力されたとき、3ストリームの一番新しい値がタプルにとして再出力される。
combineLatestの詳しい説明は、こちらを参考ください。

②バリデーションチェック

.map {
    let username = $0.0
    let password = $0.1
    let passwordAgain = $0.2

    guard username.count > 0, password.count > 0, passwordAgain.count > 0 else { return false }
    guard password == passwordAgain else { return false }
    return true
}

mapを使ってusernamepasswordpasswordAgainの文字数をチェックする。
$password$passwordAgainが一致するのをチェックする。
両方チェックOKのみtrueを出力する。それ以外はfalseを出力する。

③画面更新

.assign(to: \.readyToCreate, on: self)

mapで出力されたパスワードチェックの結果をreadyToCreateに設置する。
readyToCreateが更新されると、View側も更新される。

④AnyCancellable

.store(in: &anyCancellable)

AnyCancellableをストアする。

まとめ

SwiftUIとCombineを使えば、簡単にバリデーションチェックを作れます。
両方ともiOS13以降しか使えないのがネックですが、今後は大きく期待できると思います。
(みんなFlutterで盛り上がっているみたいですが。。。)

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

Xcode で Canvas を上手に利用するための Tips

記事内容

Xcode 12 では Canvas の機能を使って、コーディングしながらリアルタイムにデバイスの画面がどのように表示されるのかを確認できます。

この記事では、その便利な Canvas をさらにより良く使いこなすための Tips(ヒント)を掲載します。良い Tips があれば随時追加していく予定です。

もくじ

  • @EnvironmentObjectのプロパティを持つViewを表示するには
  • @Bindingのプロパティを持つViewを表示するには
  • コンポーネント用のViewだけを表示するには

@EnvironmentObjectのプロパティを持つViewを表示するには

ObservableObject クラス内の @Published 付きプロパティの値を 他のView から常に参照するために、そのクラスのインスタンスを @EnvironmentObject プロパティラッパーをつけて View 内に作成している場合、そのViewのプレビューには、以下のようにViewのインスタンスに、.environmentObject(ClassName())モディファイアをつける必要があります。これをつけないと Resume ボタンをクリックしても Canvas に表示されず、エラーになります。

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(Manager())
    }
}

@Bindingのプロパティを持つViewを表示するには

@Bindingプロパティラッパーが付いているプロパティを持つ構造体の場合、そのプロパティはデータ型だけ指定され、値は代入されていません。例えば以下のように。

@Binding var name: String

このようなプロパティを持つ構造体のプレビューをCanvasに表示したい時、イニシャライズ時に適当な引数を入れてもエラーになります。このような場合は以下のように、引数として.constant()を指定するとうまく表示されます。これはView内のあらゆるバインディングで、何か値を仮に入れたい場合に使えます。プレビューのコードは以下のようになります。

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(name: .constant("Masanao Sako"))
    }
}

Paul Hudson さんの Hacking with Swift もご参照ください。
https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-constant-bindings

コンポーネント用のViewだけを表示するには

メインのViewに配置するコンポーネント用のView(画面のパーツ)を作成している場合、コンポーネント用のView単体をデバイススクリーン大のCanvasに表示するより、そのコンポーネント用Viewのみをジャストサイズで表示したい場合は、以下のように .previewLayout(.sizeThatFits) モディファイアをつけるとViewのジャストサイズになります。

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(Manager())
            .previewLayout(.sizeThatFits)
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] あなたのDictionaryは本当にCodableなの?

概要

言いたいこと: 「思った通りにDictionaryをencode/decodeできないことがあるよ!」

前提

この記事では分かりやすさのためJSONへの変換を考えます。特に断りがなければencoderは次のものとします。また、printJSONという関数も定義しておきます。

JSONEncoder.swift
let encoder = JSONEncoder()
encoder.outputFormatting = [
  .prettyPrinted,
  .sortedKeys,
  .withoutEscapingSlashes,
]

func printJSON<T>(_ object: T) throws where T: Encodable {
  print(String(data: try encoder.encode(object), encoding: .utf8)!)
}

そもそもDictionaryCodableになるの?

"Dictionary | Apple Developer Documentation"のConforms Toの項目には、

Decodable
- Conforms when Key conforms to Decodable and Value conforms to Decodable.
Encodable
- Conforms when Key conforms to Encodable and Value conforms to Encodable.

ということが書かれています。
CodableEncodable & Decodableのことなので、KeyValue両方がCodableに適合していれば、Dictionary<Key, Value>Codableになるということになります。

例: KeyValueStringの場合

StringはもともとCodableなので、Dictionary<String, String>Codableになるはずです。
実際のコードをみてみましょう:

EncodeStringDictionary.swift
let dictionary: Dictionary<String, String> = [
  "key0": "value0",
  "key1": "value1",
]

try printJSON(dictionary)

とても簡単。これを実行すると次のようにJSONが表示されるはずです。

{
  "key0" : "value0",
  "key1" : "value1"
}

期待通りですね。

独自のKeyにしてみよう!

KeyCodableなら(ValueCodableである限り)DictionaryCodableになるとのことでした。そこで、Codableに適合する独自の型をKeyにしてみましょう。

MyKey.swift
enum MyKey: String, Codable {
  case key0
  case key1
}

すごく単純なenumMyKeyです1

では、このMyKeyKeyとするDictionaryをencodeしてみましょう!
コードは次のようになります:

EncodeMyKeyDictionary.swift
enum MyKey: String, Codable {
  case key0
  case key1
}

let dictionary: Dictionary<MyKey, String> = [
  .key0: "value0",
  .key1: "value1",
]

try printJSON(dictionary)

結果は…

[
  "key0",
  "value0",
  "key1",
  "value1"
]

!?

KeyValueがフラットに並んだ配列になっているんですけど!
Dictionary<String, String>のときと同じ結果になることを期待したんですけど!

なぜフラットな配列になるのか?

答えは公式ドキュメントに書かれています:

If the dictionary uses String or Int keys, the contents are encoded in a keyed container. Otherwise, the contents are encoded as alternating key-value pairs in an unkeyed container.

(YOCKOW拙訳) StringIntをキーとして用いている場合、ディクショナリの中身はキーで紐づけられたコンテナにエンコードされます。それ以外の場合、キーで紐づけられないコンテナにキーと値のペアが交互にエンコードされます。

すなわち、上記の"EncodeMyKeyDictionary.swift"は、KeyStringでもIntでもないため、キーと値が交互に並ぶ配列としてエンコードされてしまうのです2

フラットな配列はJSONDecoderを使えばDictionary<MyKey, String>にデコードできるのですが、Dictionaryなのにエンコードされると配列になるというのは気持ち悪いですね。Foundation以外のライブラリやSwift以外の言語と連携するときは気をつけなければいけません。

やっぱりDictionaryはオブジェクト(連想配列)にしたい

世界中にそう思っている人がいるはずで、Swift JIRAにも該当の項目があります: SR-7788
そこで私めもコメントさせていただいたわけなのですが、これは設計段階でのミスに思えます。DictionaryCodableにしたかったらKeyが適合すべきはCodableではなくCodingKeyのはずなのです。しかし、今から変えようとするとAPIが破壊的変更となってしまうため、実現可能性はかなり低いでしょう。

いくつか選択肢はありますが、SwiftCodableDictionaryを利用するという手があります。Dictionaryの代わりにこのモジュールのCodableDictionaryを利用することで、期待通りのエンコード/デコードができるはずです。

WithSwiftCodableDictionary.swift
import Foundation
import CodableDictionary

enum MyKey: String, CodableDictionaryKey {
  case key0
  case key1
}

let dictionary: CodableDictionary<MyKey, String> = [
  .key0: "value0",
  .key1: "value1",
]

try printJSON(dictionary) // -> 期待通り

まとめ

以上、SwiftCodableDictionaryの宣伝でした(え?。


  1. StringRawValueとするRawRepresentableとすることで、Codableの実装を自分でせずに済みます。 

  2. 実際の実装はGitHubで見ることができます: https://github.com/apple/swift/blob/4dab4c235b975f9b092dac504f0546bf3a5d54e1/stdlib/public/core/Codable.swift#L5523-L5560 

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

JSONの比較

Model層のCodableなどのテストコードを書いていて期待値のJSON文字列を比較したい場合がある。
しかし、JSONなどKeyValue形式のフォーマットは順不同で単純に文字列比較することができない。

そこで思いついたのがDictionary型にして比較する方法。

extension String {
    func jsonDictionary() throws -> [String: AnyHashable]? {
        guard let data = self.data(using: .utf8) else { return nil }
        return try data.jsonDictionary()
    }
}

extension Data {
    func jsonDictionary() throws -> [String: AnyHashable]? {
        return try JSONSerialization.jsonObject(with: self, options: []) as? [String: AnyHashable]
    }
}

Dataのextensionとして JSONSerialization.jsonObject した結果を返すfuncを用意しておき、Stringのextensionでもそれを通すfuncを追加しておくと例えば以下のようなテストコードでもXCTAssertEqualで評価することができる。

    func test_HogeEncode() {
        let object = Hoge(value: 123, flag: true)
        let expectation = """
            {
                "value": "123",
                "flag": "true"
            }
        """
        let data = try? JSONEncoder().encode(object)
        XCTAssertNotNil(data)

        XCTAssertEqual(try? expectation.jsonDictionary(), try? data!.jsonDictionary())
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】switchはenumの判定で使うことが多いよっていう話

if文で書くか、switch文で書くか悩むことはあるかと。
正直どっちでも良いのですが、enumのcase判定をするときは
switch文を使うことが多いです。

enumのcaseをswitchで判定する

具体的には以下のようなコードです。

enum Direction {
  case top
  case right
  case bottom
  case left
}

var direction:Direction?
//例えば何らかの条件式や処理でdirectionにrightを代入し...
direction = .right

//switch文で判定する
switch direction {
  case .top:
    print("direction = top")
  case .right:
    print("direction = right")
  case .bottom:
    print("direction = bottom")
  case .left:
    print("direction = left")
}

なんでenumを使うの?

決められたパターンのみ選択可能にすることで、
保守性や可読性をあげる目的で使っています。

自由度が高いほど良いように思えますが、プログラミングではかえって
デメリットが大きくなります。自由度が高いということは、
各々が自由な書き方をすることができ、複雑なコードになりがちです。
あえて制約をつけることで統一性を担保し、保守性をあげることができます。

そして本題のswitch文はenumの判定で使うことが多いよという話ですが、
これはあくまで一個人のやり方であって、全ての現場でそういう使い方が
されているわけではありませんので参考程度にすると良いかもしれません。

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

[Swift5]ViewControllerからModelに通信を行って値を渡す方法

ViewControllerの役割

VIewControllerは、名前の通り表示されるViewを管理・操作(表示・非表示・配置・アニメーションなど)をする役割を持つクラスです。

あとは、受け取ったデータに合わせてTextやViewを表示・管理する役目を持ちます。

Modelの役割

システムの中でビジネスロジックを担当しており、実際にデータの処理を行うのがModelの役割です。
例えばアピの処理をおこなうのはModelです。

MVCモデルについて

アプリ開発におけるアーキテクチャの一つです。

MVCモデルの他にMVP、MVVM、などがあります。MVCモデルに関しての詳細は下記urlで取り上げられております。
https://qiita.com/s_emoto/items/975cc38a3e0de462966a

Modelの準備

まず、Model側でclassを作成しプロパティとinit(イニシャライザ(初期値))を用意します。

SampleModel.swift
class SampleModel {

    //Controllerから渡ってきた値を入れるプロパティ
    var sampleValueA: String?
    var sampleValueB: String?
    var sampleValueC: String?

    //Controllerから値を受け取る(イニシャライザ(初期値))
    init(firstSampleValue: String, secondSampleValue: String, thirdSampleValue: String) {

        sampleValueA = firstSampleValue
        sampleValueB = secondSampleValue
        sampleValueC = thirdSampleValue
    }

Controller側の記述

先にコードを記述します。

ViewController.swift
class SampleViewController: UIViewController {

    //SampleModelへ渡す値
    firstSampleValue  = "firstSampleValue"
    secondSampleValue = "secondSampleValue"
    thirdSampleValue  = "thirdSampleValue"

    override func viewDidLoad() {
        super.viewDidLoad()

        startSampleModel()
    }

    //SampleModelと通信をおこなうメソッド
    func startSampleModel() {

      let sampleModel = SampleModel(firstSampleValue: firstSampleValue, secondSampleValue: secondSampleValue, thirdSampleValue: thirdSampleValue)
    }
}

Modelで作成したinitをController側で呼び出して、Controllerで作成したプロパティをModelのinitに入れてあげます。

そうすると、Modelのinitの中で処理が行われ、今回のケースの場合、結果的にControllerで作成した文字列"firstSampleValue"sampleValueAに、文字列"secondSampleValue"が文字列sampleValueBに、文字列 "thirdSampleValue"sampleValueCに入ります。

これでControllerからModelへ値を渡すことができます。

最後に

次回の投稿はModelからControllerへ値を返す方法について投稿します。
今回の記事のの続編とです。

是非参考にしてください!

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

【初学者/自分用】未経験者が学ぶべきSwift基礎 レベル1:実務は厳しい

はじめに

https://qiita.com/Nekosennshi/items/2bb63a0cc92c9afa9fb9
こちらの記事を元に作成しています。

それでは早速始めていきます。

(それぞれの項目にあるURLは参考にした記事です。)

レベル1:実務は厳しい

演算(+, -, *, /, %)

特殊な効果を持つ記号とか文字のことを『演算子』と呼びます。
プログラミングの世界では「1 + 1の演算式は2を返す」という言い方をします。

演算の対象となる値や数値や変数の事を『オペランド』といいます。
例えば「1 + 2」の場合は数値『1』と数値『2』が『オペランド』になります。

他言語ではお作法のような感じですが、Swiftでは必ず『演算子』の両側に半角スペースを置く必要があります。
Swift側で上手く『演算子』として認識してくれない可能性があります。思わぬバグの原因になりがちなので要注意です。

Swiftの『+』記号は加算以外に文字列連結を行うことができます。使い方は簡単で文字列と文字列の間に『+』を置くだけでOKです。

print(1 + 1)
print("あなた" + "が好きです。")

2
あなたが好きです。

Swiftでの四則演算、加算は『+』、減算は『-』、乗算は『*』、除算は『/』、そして剰余(割り算した時の余り)は『%』を使用します。

print(1 + 2) #『1 + 2』の演算結果を出力します。
print(3 - 2) #『3 - 2』の演算結果を出力します。
print(3 * 2) #『3 × 2』の演算結果を出力します。
print(6 / 2) #『6 ÷ 2』の演算結果を出力します。
print(6 % 5) #『6 ÷ 5』の余りを出力します。

Swiftでも『+=』や『-=』といったように『加算』や『減算』とかをしつつ、同時に『代入』を行う演算子が用意されています。

var n = 1
n += 1 #変数『n』に数値『1』を加算しつつ自身に代入します。
print(n) #数値『2』が出力されます。

var n1 = 1
n1 -= 1 #変数『n』に数値『1』を減算しつつ自身に代入します。
print(n1) #数値『0』が出力されます。

var n2 = 1
n2 *= 3 #変数『n』に数値『3』を乗算しつつ自身に代入します。
print(n2) #数値『3』が出力されます。

var n3 = 10
n3 /= 2 #変数『n』に数値『2』で除算しつつ自身に代入します。
print(n3) #数値『5』が出力されます。

var n4 = 7
n4 %= 5 #変数『n』に数値『5』で剰余演算しつつ自身に代入します。
print(n4) #数値『2』が出力されます。

インスタンスとは

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r30
クラスは設計図みたいな存在なので、クラスを定義した時点では実体がないです。

そのため、中に書いてある処理を実行するためには、その設計図を元にした実体を生成しないといけません。これが『インスタンス』と呼ばれるものになります。『インスタンス』を生成するにはこんな感じで記述します。

class Test {
    let n = 1
    var _n = 2

    func f(){
        print(n)
        print(_n)
    }
}

Test() #クラス『Test』を元にした実体を作ります。これが『インスタンス』です。

『インスタンス.識別子』とすることで、そのデータにアクセスすることができます。

print(Test().n) #クラス『Test』を元に生成した『インスタンス』の中にある定数『n』の中身の『1』を出力します。

クラス『Test』の中にある関数『f』を実行させるとなるとなるとこうなります。

Test().f() #クラス『Test』を元に生成した『インスタンス』の中にある関数『f』を実行します。

プロパティとは

https://qiita.com/akeome/items/2197a635ac616ab2f8e2
※プロパティは非常に細かいため、詳細は参考記事をご覧ください。

プロパティとは型(クラス、構造体、列挙型)を構成する要素の一つで、型もしくは型のインスタンスに紐付いた値を指します。
ちなみに型を構成する他の要素としてはメソッド、イニシャライザ、サブスクリプト、ネスト型があります。

struct Person {
    var name = "Conan" #プロパティ

    func greet() { #メソッド
        print("I'm \(self.name)")
    }
}

メソッドとは

https://qiita.com/kiyotaman/items/c5d5e9e0084cf14ad0f6
メソッドは、関数と同じように戻り値を返すこともできますが、基本的にはインスタンスのプロパティを操作する方法として使う。

細かく言うと、、、
メソッドは特定の型をもつ関数のこと。
クラスや構造体や列挙型にインスタンスメソッドを定義できる。
メソッドには型を定義できる。
自分自身を型として関連付ける事もできる。
タイプメソッドはObjCのクラスメソッドに似ている。
構造体と列挙型に型を指定する事ができる。

比較演算子(==, !=)

https://i-app-tec.com/ios/swift-operator.html
数値の大きさを比較する演算子です。結果はBool値(true, false)です

Bool値とは、
真を表すtrueか偽を表すfalseのいずれかの値をとる型です。
真か偽を表す値を真理値といいます。

>   より上
<   より下
==  イコール
>=  より以上
<=  より以下
!=  ノットイコール
var a = 4
var b = 7
var c: Bool

c = (a > b)
c = (a < b)

c = (a >= b)
c = (a <= b)

c = (a == b)
c = (a != b)

補足:論理演算子

https://i-app-tec.com/ios/swift-operator.html
比較してBool値を返します。

&&  AND
||  OR
!   NOT
var d = true
var e = false
var f: Bool

f = (d && e)
f = (d || e)

f = (!d)
f = (!e)

変数 var

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r10
『変数』=「データを一時的に保存できる加工や複製が可能な名前がついた領域」

参考書などでは「箱」と例えられる事が多いですね。
箱で例えると「データを一時的に保存できる加工や複製が可能な名前がついた箱」って感じです。名前をつけられて加工したり複製することができる、というのがポイントです。

言葉ではわかりづらい方は、数学の方程式で表します。
x + 1 = y
この概念と非常に近く、プログラム言語の『変数』には、数値とか文字列とか真偽値とか様々なものを入れられます。

下記は、『s』という『変数』が『String型』で作られました。
『var』は『変数』を初めて作るときに"必ず"記述しないといけません。
コンピューターに「変数作るよー!」って教えてあげてるようなイメージですね。

var s: String
s = "Hello world" #文字列『Hello world』を代入してあげます。

print(s) #『Hello world』が出力される。
print("s") #変数『s』ではなく文字列『s』になるので『s』が出力されます。

『変数』の中に数値を入れた場合はそのまま計算できる。

var n: Int
var _n: Int

n = 1
_n = 2

print(n + _n) #数値『3』が出力されます。

『変数』は上書きすることができます。
注意点として、上書きするときも同じデータ型の値をいれてあげないとエラーになります。

var s: String

s = "Hello world"
s = "Hello Swift" #変数『s』を上書きします。この代入処理が最後に行われます。

print(s) #『Hello Swift』が出力されます。
---------------------------------------
var n: Int

n = 0
n = 1 #変数『n』を上書きします。この代入処理が最後に行われます。
n = "Hello world" #エラーです。変数『n』はInt型の値しか入りません。

これまで『変数』を生成して、その後に値を代入をするという流れでしたが、『変数』を生成すると同時に代入することもできます。
『変数』の生成と同時に何かの値を代入することを『初期化』と呼びます。
そして代入された値は『初期値』と呼ばれます。

var s: String = "Hello world" #『変数』の生成と同時に文字列『Hello world』を代入します。
#↑1文が初期化。『Hello world』が初期値。

print(s) #文字列『Hello world』が出力されます。

今まで『変数名: データ型』で型の指定をしていました。

⚠️重要:"『初期化』を行った場合は、データ型の指定をしなくても変数が生成できます。"

『初期化』を行った場合は下記のように型の指定をしなくても『変数』が生成でき、『初期値』に対する適切なデータ型を自動で設定してくれます。

文字列『Hello world』が『初期値』なのでその値に該当する適切なデータ型である『String型』が指定されているものとして生成されます。

var s = "Hello world" #ここに注目して下さい。String型が自動指定されている。

print(s) #文字列『Hello world』が出力されます。

定数 let

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r10
変数 var と違い、一度代入した値を上書きすることができない。「変更ができない変数」というイメージですね。
文法は『変数』の時と同じような感じになります。

#『初期化』した『定数』を上書きする。
let n = 1
let _n: Int = 2
n = 2 #エラーです。

#『初期化』しないで『変数』を生成してその後に代入する。
let s: String
s = 1 #OKです。
s = 2 #エラーです。

エスケープ処理「()」

通常の文字の集まりのことを『文字列』といいます。

print("Hello world") # ""←ダブルクォーテーションが必要。

大体の文字は『文字列』として扱えますが、例外があります。
『"』を文字列として扱いたい、となった場合は『"』が3つ連続してる感じです。
他のプログラム言語でも言えることですが、『"』を『"』で囲うことはできません。

これは『文字列』をくくるために使ってる『"』なのか、ただの文字としての『"』なのか、コンピューター側で上手く判断できないからこんな動きになります。

print(""") #エラーです。

「じゃあ『"』を普通の文字みたいな扱いにしたいときはどうするのー?」ってなりますよね。
そんな悩めるあなたは『\』(バックスラッシュ)を使用してみましょう。
(※『\』=『option + ¥』)

print("\"")

" #実行結果

『\』を何の機能も持たない『\』にしたい、といった場合は『\』と記述しちゃえばOK。

print("\\")

\ #実行結果

『"』などの特殊な記号の手前に『\』を置いて何の機能も持たないただの文字として扱わせることを「エスケープする」と言います。

『\』には『"』のような特殊な機能を持つ記号の機能を無くす機能があります。

特定の文字と組み合わせることで他の表現もできたりします。
『\n』と記述することで改行を表現することができます。

print("あいう\nえお")

あいう #実行結果
えお

『\t』でタブ文字を表現することができます。

print("あいう\tえお")

あいう   えお #実行結果

『\"』とか『\』とか『\n』とか『\t』とかのことを『エスケープシーケンス』(エスケープ文字)って呼ばれます。

if文

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r24
『if文』に必要な、真偽値とは↓
https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r8
『if文』は「もし〜だったら〜を行う」といった分岐処理のこと。
『条件式』が『true』ならば『{}』の中身が実行され、『false』ならば実行されない、といった処理を構築できるのが『if文』です。

if 条件式 {
    #ここに処理...
}

『else文』は最初の『if文』の『条件式』が『false』となった場合に問答無用で『else文』の『{}』の中身が実行される、といった処理になります。
下記は、最初の『if文』の『条件式』が『false』なので『print("実行されました。その2")』が実行されます。

if false {
    print("実行されました。その1")
}
else {
    print("実行されました。その2")
}

『else文』ではその前の『if文』が『false』ならば問答無用で実行されますが、『else if文』だとそのあとに更に処理を分岐させることができるといった違いです。

『else if文』と『else文』を複数組み合わせることができます。

まず『if文』で条件分岐が行われ、その後に『else if文』でさらに条件分岐、その後全てが『false』ならば『else文』の処理が実行される、といった流れになります。

最初は必ず『if文』、最後は必ず『else文』です。

if false {
    print("実行されました。その1")
}
else if false {
    print("実行されました。その2")
}
else if false {
    print("実行されました。その3")
}
else {
    print("実行されました。その4")
}

今まで『条件式』のところに直接『true』や『false』を記述しましたが、あまり意味はないので演算式を用います。
『1 < 2』と記述した場合、これは『true』なので『print("実行されました。")』が実行される、といった感じになります。

if 1 < 2 {
    print("実行されました。")
}

⚠️重要:他言語と異なり、Swiftは『条件式』の演算結果が真偽値でない場合はエラーになります。

if 1 { #数値『1』は真偽値ではないのでエラーです。
}

switch文

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r26
『switch文』では同時に多数の分岐処理をさせていくことができます。
『case』を使った処理は『case文』と呼ばれ、『case文』は複数設置することができます。

let n = 1 #定数『n』に『1』を代入

switch n { #定数『n』が『条件式』。ここに評価させたい定数や変数や演算式などを入れる
    case 1: #『条件式』に書いてある定数『n』の中身が『1』だった場合に『:』以降の処理を実行。
        print("1です。")

    case 2: #『条件式』に書いてある定数『n』の中身が『2』だった場合に『:』以降の処理を実行。
        print("2です。")

    default:
        print("defaultです。")
}

最後の『default』は、その前の『case』での評価がすべて成立しなかった場合に実行されます。
『default』を使った処理は『default文』と呼ばれます。

let n = 3

switch n {
    case 1:
        print("1です。")

    case 2:
        print("2です。")

    default: #ここが実行されます。
        print("defaultです。")
}

『条件式』の演算が最初に行われるため、『1 + 1』の演算結果の『2』が比較対象となります。
その後に各『case文』と一致するかどうかの判定が上から順番に行われます。上から順番に行われる、というわけなので以下のように『case 2』を複数用意しても実行されるのは最初の『case 2』となります。

switch 1 + 1 {
    case 2: #実行されるのはここのみです。
        print("2です。")

    case 2:
        print("2です。その2")

    case 2:
        print("2です。その3")

    default:
        print("defaultです。")
}

基本的に『default文』を記述する必要はありますが、
「『条件式』の全ての結果が『case文』に記述されている場合のみ『default文』の省略ができる。」

しかし、「全ての『条件式』の結果を網羅する」というのが不可能だったりする場合が多いため、『default文』は必ず記述しておく、というイメージを持っておいた方が無難な気がします。

『default文』は必ず『switch文』の最後に記述する必要があります。

switch 1 { #これはOKです。
    case 1:
        print("1です。")
}

『case文』や『default文』の中身を空にすることはできず、『break文』というのを使います。
『break』は「『switch文』とかループ処理とかを抜けて!」という命令になるので、『break』と記述すれば処理を何も行わず『switch文』を抜けることが可能になります。

let n = 1

switch n {
    case 1:
        break #『break』と記述すると『switch文』を抜けることができます。「何もしたくない」という意味。

    case 2:
        print("2です。")

    default:
        print("defaultです。")
}

他言語と違い、Swiftの場合は「続く処理が全て実行される」という挙動にならないため、各『case文』ごとの『break』は記述する必要がありません。

逆に「続く処理を実行したい」場合、『fallthrough』と記述します。
『fallthrough』と記述した場合は、「次の『case文』または『default文』の処理のみが問答無用で実行される」という挙動になるので要注意です。

let n = 1

switch n {
    case 1:
        print("1です。")
        fallthrough

    case 2:
        print("2です。")
        fallthrough

    case 3:
        print("3です。")
        fallthrough

    default:
        print("defaultです。")
}

1です。
2です。
3です。
defaultです。

この定数『n』はInt型となっているため、文字列とかを入れることはできません。
『case "1"』のように、『case文』でデータ型が違う値と比較させようとするとその時点でエラーになります。

let n : Int = 1

switch n {
    case "1": #ここでエラーが出ます。
        print("1です。")

    default:
        print("defaultです。")
}

for文

https://techacademy.jp/magazine/14546
https://qiita.com/funacchi/items/4da8016b2d1e13cec538
for 文では条件を記述する代わりに、繰り返しの元となる範囲や配列を in の後に記述します。

範囲は 0…10 や 0..<10 のように 「…」「..<」を使った記法を利用できます。

『開始値…終了値』:「開始値」で始まり「終了値」まで続く範囲を指定します。「終了値」も範囲に含まれる。
『開始値..<終了値』:「開始値」で始まり「終了値」のひとつ前まで続く範囲を指定します。「終了値」は範囲に含まれない。
『範囲』や『配列』から元になる値が生成されて、それが『for』の後ろに記述した『定数』に代入されます。

for 変数 in 開始値 ..< 終了値 { #..<のため、終了値は含まない。
  #繰り返し実行されるコード
}

構造体:struct

https://qiita.com/Howasuto/items/ef02b0682f2222b3c391
https://qiita.com/Howasuto/items/57acf33b40dbf4604397
※構造体(struct)は非常に細かいため、詳細は参考記事をご覧ください。

値型の一種で、ストアドプロパティの組み合わせによって1つの値を表すことができるもの。
簡単に言うと、これは継承不可のクラスのようなものであり以下のような特徴を持っています。

・複数の値を一個の構造体で管理できる(ストアドプロパティの組み合わせによって一つの値を表す)
・辞書とは違い、同じ構造のオブジェクトを複数生成可能
・クラスと違い参照不可
・クラスは参照型に対し、構造体は値型
・イニシャライザも関数も使用可能

このような構造体の用途は多くあり、例えば画面上の幅と高さは2つのプロパティをもつ構造体で表すことができ、統一して管理したい複数のプロパティを使用する際に便利です。

struct 構造体名 {
  構造体の定義
}

関数 func

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r18
関数は「処理(演算)に名前をつけていつでも操作したり呼び出せるようにしたもの」というイメージになります。

下記は『print(1 + 1)』という処理に『test』という名前を付けていつでも呼び出せるようにした形です。

文頭の『func』は「関数を定義するよ!」ってコンピューターに教えてあげる宣言のような感じになり、『関数』を定義する際には"必ず"記述する必要があります。
変数とか定数を定義するときに使った『var』とか『let』とかと同じような感じですね。

『func』の後に『関数名』を記述します。今回は『test』が該当しますね。

func test() {
    print(1 + 1)
}

test() #数値『2』が出力されます。

『関数名()』とすることで定義した関数を呼び出して実行させることができます。
『()』を付けるのを忘れないようにしてください、これが『関数』を実行させる際のトリガーのようなものになります。

※私は『()』をよく忘れていました。?

型キャスト

https://fukatsu.tech/swift-cast

キャストとは『変数』や『定数』の型を、より具体的な型や汎用的な型として扱うことです。

『Any型』であった変数や定数を→『Int型』
『Int型』だった変数や定数→『Any型』
として扱うような操作です。

キャストには『アップキャスト』と『ダウンキャスト』の2種類があります。

アップキャスト

より汎用的な型として扱うことを指します。

例えば、たかし君は東京都民です。
東京都民であるならば、日本人でもあるので、日本人としての振る舞いもします。
このような東京都民から日本人というように、より広いくくりとして扱う場合に、アップキャストを使います。

『アップキャストしたい値 as アップキャストする型』という形式で書く。

let string: String = "sample"
let any: Any = string as Any #String型からAny型へアップキャスト

ダウンキャスト

より具体的な型として扱うことを指します。

先程のたかし君の例をもう一度考えてみます。
「東京都民であるたかし君は、日本人である」というのが先程の例でしたが、その逆を考えてみます。
「日本人であるたかし君は、東京都民である」
これは、たかし君は東京都民である可能性もありますが、もしかしたら大阪府民である可能性もあります。

⚠️重要:『アップキャストは必ず成り立っていた』のに対して、『ダウンキャストは必ずしも成り立つわけではない』
⚠️重要:失敗する可能性を含んでいます

そのため、文法としても「失敗を含む前提のもの」を前提に置きましょう。

ダウンキャストの書き方は『as?』か『as!』を使う2種類の方法があります。

as?

3行目でInt型へダウンキャストをしようとしますが、"sample"はInt型にはなりえないので失敗します。
その場合に値がnilとなるのがas?の特徴です。

それに付随して、2行目を見てみると、こちらはString型へのダウンキャストが成功していますが、値はString型のオプショナル型となっています。
これは失敗する可能性があるので結果がオプショナル型になります。
オプショナル型→https://fukatsu.tech/optional-swift

let any: Any = "sample" as Any
let string = any as? String #オプショナル("sample")
let int = any as? Int #nil

as!

書き方自体はas?と変わってはいませんが、結果が異なります。

2行目がas?の場合には『オプショナル型』だったのが『String型』になっています。
加えて、3行目で実行時エラーとなっています。

オプショナル型が分かると検討がついたかもしれませんが、as!を使うと、
結果が強制的にアンラップされる
→そして結果がオプショナル型にならない
→失敗した場合に実行時エラー
→アプリがクラッシュ

このように、as!を使ったダウンキャストを強制キャストといいます。

let any: Any = "sample" as Any
let string = any as! String #"sample"
let int = any as! Int #実行時エラー

どちらを使うべきか?
as?→安全である代わりに、失敗したときの処理を書く必要がある。
as!→失敗したときの処理を書かなくていい代わりに、実行時エラーの危険性がある。

基本的には安全であるas?を使うのがいいと思われます。
しかし、as!を使うほうがむしろ好ましい場面もいくつかあります。

例えばTableViewでTableViewCellを呼び出す際の処理です。
「処理が失敗しない前提」
「万が一失敗したとしても開発環境の時点でクラッシュしたほうが気づきやすい」
という利点を考慮してas!と書くようにする。

他にも、UI周りのパーツのインスタンス化する処理ではas!を使うようにしたり。
このように、両方の特徴をしっかり理解した上で、チーム内などで基準を設けたりして適切な方を選ぶことが重要だと思います。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
    return cell
}

レベルX:一番重要かも

間違いない。

・ぐぐり力
・ある程度のエラーを自己解決できる
・難題に見切りをつけ、「何がどうわからず、どう調べたが、どうわからなかったので
 こういうことを知りたい」と言った形で質問ができる
・積極的に自習ができる
・プログラミングが好き

終わりに

お疲れ様でした!いかがだったでしょうか?

私は覚えること多いな〜と思いながら、この記事を書いていました笑
ですが、記事を書いているとものすごく理解して覚えることができました。

仕事で技術職のエンジニアを目指すなら、技術力なんて最低限は必須ですよね。
皆さんもインプットだけではなく、アウトプットをしながら学習することをおすすめします。

筆者も初学者ですので、未経験からエンジニアになるための人助けに少しでもなればいいと思い書きました。
少しでも参考になれば幸いです。☺️

下の『ストック』や『LGTM』をお忘れずに???それでは

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

【初学者/自分用】未経験者が学ぶべきSwift基礎

はじめに

未経験の方は、エンジニアを目指すには何を学習すればいいのかわからない。
そのため、ロードマップが必要だと考えました。

https://qiita.com/Sossiii/items/e2c846d12f2f5db7c426
こちらの記事を参考にさせていただき、基礎から着実に学んでいこうと思います。

基礎の学習は、「レベル5:独学はもう効率悪いから今すぐ実務に入ろう」に到達した時点で実務を推奨されているので、この記事ではレベル5までの内容を記述していくことにします。
(※もちろん技術力が高い方が、未経験者の就職・転職はしやすいと思います。あくまで参考記事を目安します。)

最後にも述べますが、基礎を学び続けるよりもポートフォリオを作った方がインプット、アウトプット、就活・転職を効率よくできると思うので、一通り学んだらすぐにでもポートフォリオ作成に移ってしまいましょう!

※量が多かったので、見やすさを重視するために分散させました。?‍♂️
レベルごとにURLを飛んでいただくと、閲覧可能です。

それでは早速見ていきます

レベル1:実務は厳しい

https://qiita.com/Nekosennshi/items/9dcb4a0dcb7323577153

・演算(+, -, *, /, %)
・インスタンスとは
・プロパティとは
・メソッドとは
・比較演算子(==, !=)
・変数 var
・定数 let
・エスケープ処理「()」
・if文
・switch文
・for文
・構造体:struct
・関数 func
・型キャスト

レベル2:数打てばどこか入れるかも

※作成段階です。?‍♂️

・型推論
・オプショナルがわかる
・if let
・オプショナルチェーン
・画像が使える
・storyboard/コーディングによる描画
 View、TableView、CollectionView、Switch、SegmentedControl
 TableViewとCollectionViewでのカスタムセルによる実装、
 その他もろもろのパーツ
・画面遷移(segue, navigationController,等)
・参照渡し, 値渡し
・アラート UIAlertController,UIAlertAction
・SVProgressHUDを適切なタイミングで使える
・NavigationControllerが使える

レベル3:数打てばどこか入れるかも

※作成段階です。?‍♂️

・UIView.animateでアニメーションが実装できる
 -> ライフサイクルを意識できると良い
   これができるとUIに磨きがかかるので選考でウケがいい
・セルの再利用がわかり、適切に動作させることができる
 -> 再利用を理解せず(特に初期化)、副作用が起きがち
・selfをつける時、要らない時がわかる

レベル4:入れる確率上昇中

※作成段階です。?‍♂️

・非同期処理(WebAPIを利用)->DispatchQueueやクロージャを使う
 (クロージャはここで使えれば良さそう)
・ライブラリ(Cocoapodsで外部ファイルをインストールして使う)
 Charts、FSCalendar、SwiftyJSON、とかで良さそう
・Delegateが実用できる
・NotificationCenterが使える
・Githubが使える
・SourceTreeが使える

レベル5:独学はもう効率悪いから今すぐ実務に入ろう

※作成段階です。?‍♂️

・Firestore、RealmSwiftを使える
 サーバー上に保存した情報をローカルDBに入れることで、
 何回もFirestoreからデータを引っ張らなくても良い(通信が減る)
 RealmSwiftはメモリ上に保存するのでタスクキルでデータは消え、肥大しない
・チャットが実装できる
 ライブラリ-> MessageKit
・ある程度のUIが設計できる
Pinterestでググれ

終わりに

お疲れ様でした!いかがだったでしょうか?

私は覚えること多いな〜と思いながら、この記事を書いていました笑
ですが、記事を書いていると、すごく理解して覚えることができました。

仕事で技術職のエンジニアを目指すなら、技術力なんて最低限は必須ですよね。
皆さんもインプットだけではなく、アウトプットをしながら学習することをおすすめします。

筆者も初学者ですので、未経験からエンジニアになるための人助けに少しでもなればいいと思い書きました。
少しでも参考になれば幸いです。☺️

下の『ストック』や『LGTM』をお忘れずに???それでは

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

WKWebViewでクロスサイトトラッキングの仕組みを利用できないのはつらいのでITPを無効にしてみる

はじめに

WKWebViewでWebページ表示するiOSアプリをXcode12でビルドしたら、iOS14で挙動が変わってしまったので
それについて調査したことを書きます!

Xcode12にしたら何が起こった?

WebViewで表示しているページ内にiframeがあり、他のページを表示させていたのですが、
iOS14でiframe内のページが表示されなくなってしまいました...( ;ω; )

メインフレームとiframeで別々のドメインのページを表示していたのですが、
メインフレームのCookieを利用したiframeのページのリクエストができなくなってしまったようでした。

img01.png  img02.png
※ イメージ内のURLはサンプルです

影響があるバージョンは?

Xcode11.7でipaを作って、iOS14にインストールした場合は問題ありませんでした。
また、Xcode12でビルドした場合も、iOS14未満では問題ありませんでした。

Xcode バージョン iOS バージョン 3rd party cookieの使用可否
11.7 11.x 使用可
11.7 12.x 使用可
11.7 13.x 使用可
11.7 14.x 使用可
12.0.1 11.x 使用可
12.0.1 12.x 使用可
12.0.1 13.x 使用可
12.0.1 14.x 使用不可

iOS14SDK × iOS14だと、異なるドメインのCookieをiframeでセットできないみたいでした。
(Xcode 12.0.1以降のバージョンやβ版などでもいくつかのパターン試してみたのですが、変わらずでした。)

iOS14からITPがデフォルトで有効になっている

ここから異なるドメインのCookieをiframeでセットできない原因についてです。

ITP(Intelligent Tracking Prevention)については、他の記事などでも詳しく書かれているものがあるので詳細は省きますが、
ユーザーのプライバシー保護を目的として、トラッキングを防止する機能です。クロスサイトトラッキングを制限するものです。

iOS14からはITPがデフォルトで有効になるようでした。
以下、 https://planet.webkit.org/ より引用

Intelligent Tracking Prevention in WKWebView
Additionally in iOS 14.0 and macOS Big Sur, Intelligent Tracking Prevention (ITP), is enabled by default in all WKWebView applications. To learn more about how ITP protects users against web tracking, checkout this documentation on the topic.

In some extreme cases, users might need to disable ITP protections, for example when relying on web content outside of the app developer’s control. Applications can signal the need to allow users to disable ITP by adding a Purpose String for the key NSCrossWebsiteTrackingUsageDescription to the app’s Info.plist. When present, this key causes the application’s Settings screen to display a user control to disable ITP. The setting cannot be read or changed through API calls.

Note that applications taking the new Default Web Browser entitlement always have a user control in Settings to disable ITP, and don’t need to specify the NSCrossWebsiteTrackingUsageDescription key in their Info.plist.

Google翻訳に頼って、要約すると、、、

  • iOS14.0およびmacOSBig Surでは、ITPがすべてのWKWebViewアプリケーションでデフォルトで有効になっている。

  • アプリ開発者の制御の及ばないWebコンテンツに依存している場合は、ユーザーはITP保護を無効にする必要がある。

  • アプリケーションは、NSCrossWebsiteTrackingUsageDescriptionキーをInfo.plistに追加し、目的を記載することで、ユーザーがITPを無効にできるようになる。
    このキーが存在する場合、設定アプリ > 対象アプリに「Webサイト越えトラッキングを許可」のスイッチが表示される。
    API呼び出しを介して設定を読み取ったり変更したりすることはできない。

  • デフォルトWebブラウザの資格を取得したアプリの場合は、Info.plistにNSCrossWebsiteTrackingUsageDescriptionキーを追加する必要なく、設定アプリに「Webサイト越えトラッキングを許可」のスイッチが表示される。

ITPを無効にする機能を用意する

Info.plistに必要なキーと無効にする理由を記載すれば、ITPを無効にするスイッチを表示させることはできそうだけど、
アプリ側からITPの有効/無効の状態を取得することや変更させることはできなそう...( ;ω; )

ですが、何も対応しないわけにはいかないので、
とりあえず、Info.plistにNSCrossWebsiteTrackingUsageDescriptionキーを追加してみました。

<key>NSCrossWebsiteTrackingUsageDescription</key>
<string>XXを表示するために使用します。</string>

ビルドして、アプリを起動してみると、設定アプリに項目が表示されました!
img03.png

デフォルトではスイッチがOFF(クロスサイトトラッキングを許可しない設定)になっていますが、
これをユーザーがONに切り替えてくれることで、異なるドメインのCookieをiframeでセットできるようになりました!

さいごに

業務用のアプリケーションなどであれば、設定アプリのスイッチをONにしてくださいと事前に通達してからアップデートを配布するなど運用でカバーできるかもしれませんが、
ストアにリリースしているアプリではユーザーに設定アプリまで移動して設定を切り替えてもらうのは現実的ではないかなと思います。。。

iOSアプリ側だけで根本解決は厳しそうなので、結局Webコンテンツ側の修正をして、異なるドメインのCookieを使用しないように対応していくしかないかなと思いました。

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

switch文 範囲指定

概要

switch文の条件の範囲の指定方法について記述してみました。
自分自身switch文の条件には特定の値しか設定できないと思っていたので、自分で振り返る意味でも残しておきます。

記述

switch文は制御式の値に応じて実行する命令を切り替えるもの。

ViewController.swift
switch 制御式{
case 条件1:
    //条件1に当てはまる場合の処理
case 条件2:
    //条件2に当てはまる場合の処理
default:
    //どの条件にも当てはまらなかったっ場合の処理
}

条件に範囲がなく特定のものは問題なく記述できますが、以下のようなif文をswitch文に置き換える場合はどのようにすればいいでしょうか?

ViewController.swift
let number: Int!
if number > 0 {
    // numberが正のときの処理
} else if number < 0 {
    //numberが負のときの処理
} else {
    //numberが0のときの処理
}

このような時は、Int.minInt.maxを用います。
Int.minは整数型の取りうる最小値を表し、Int.maxは整数型の取りうる最大値を表しています。
整数の最大値、最小値と考えると+∞、−∞を思い浮かべますがデータ上は異なります。
整数の最大値は9223372036854775807、最小値は-9223372036854775807です。
桁が莫大なので実質+∞、−∞と考えていいと思います。
ということで、これを使ってswitch文を作ります。
[追記]ランダムの範囲が整数型全てであった時、1..<Int.maxではInt.maxが正の整数の範囲外になってしまうので1...Int.maxと修正しました

ViewController.swift
let number = Int.random(in: -100...100)
switch number{
case 1...Int.max:
    //numberが正のときの処理
    //[追記]1..<Int.maxの場合Int.maxが範囲外
case Int.min..<0:
    //numberが負のときの処理
default:
    //numberが0のときの処理
}

Int.maxなどを用いずに範囲指定したい場合も実行できます。
処理を行わない時はbreakで中断します。

ViewController.swift
let number = Int.random(in: 0...10)
switch number{
case 1...5:
    //numberが1~5のときの処理
case 6...10:
    //numberが6~10のときの処理
default: break
}

最後に

自分自身、あまりswitch文を用いずにif文のみで解決してきたところがあったので、
内容に間違いがあるかもしれません。その際はご指摘のほどよろしくお願いします。

参考文献

この記事は以下の情報を参考にして執筆しました。

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