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

iOS13 で UILabel のスタイルが間違えて表示されている可能性がある

起きたこと

UILabel の attributedText を nil (リセット) にしてもラベルのスタイルが変わらないという現象が発生していました。

image.png

TL;DR

原因

UILabel の attributedText の値が nil(リセット)になった場合、通常(iOS12以前)は Label の attributes もリセットされるが iOS13 ではリセットされないとの報告があり、それが起因して考えられます。

解決策

UILabel の textattributedText はどちらか一方の値が変わるともう一方の値も変わるという連動性があることから(下記ドキュメント)、今回のように textattributedText を共存させて更新することはシステムのバグに限らず望ましくないので、attributedText のみ明示的に更新処理を行うように修正したところ解決しました。

※ iOS13のバグかどうかについては現在のところ不明

具体的な発生タイミングと改善

Before

チャットに表示する UI で、コメントがリンクの場合は青く表示させて Clickable にさせるように UILabel を継承したサブラクス(以降: LinkLabel)を作成していました。基本的な仕様としては LinkLabel の text プロパティに didSet をセットし、更新されたタイミングでテキストをトリミングして attributedText を更新するという感じです。

final class LinkLabel: UILabel {
    override var text: String? {
        didSet {
            updateAttributedText()
        }
    }

    func updateAttributedText() {
        guard let text = text else {
            return
        }
        let mutableAttributedString = NSMutableAttributedString(string: text)

        // 省略....
        // いろいろ Attributes をセットする

        attributedText = mutableAttributedString
    }
}

上記で作成した LinkLabel を Cell に配置して TableView で表示した時に、Cell が再利用されるタイミングで以前の attributes がキャッシュされていて関係ないコメントも青く表示されることがたびたび起こるようになりました。

After

TL;DR にも記載してある通り、attributedText だけを明示的に更新することで改善できたので下記のように修正します。

final class LinkLabel: UILabel {
    var displayText: String?

    func setText(_ text: String?) {
        displayText = text
        updateAttributedText()
    }

    func updateAttributedText() {
        guard let text = displayText else {
            return
        }
        let mutableAttributedString = NSMutableAttributedString(string: text)

        // 省略....
        // いろいろ Attributes をセットする

        attributedText = mutableAttributedString
    }
}

使用する Cell 側でもキャッシュをリセットできるように prepareForReuse() セットします

final class CommentCell: UITableViewCell {
    let linkLabel = LinkLabel()

    override func prepareForReuse() {
        super.prepareForReuse()
        linkLabel.attributedText = nil
    }

    // 省略...
}

調べたこと

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

アクセス許可が降りていない時、設定画面に飛ぶ処理

        let url = URL(string:UIApplication.openSettingsURLString)!
        //UIApplication.shared.openURL(url as! URL) //非推奨のため、下記のものを使う
        UIApplication.shared.open(url, options: [:], completionHandler: nil) //openURlが非推奨のためこちらを使用
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift でアプリ内アイテムフィルタリングを実現する話

前書き

こんにちは、リビリンです。日本語も記事書きも初心者です。

この記事はジェネリックタイプと関数型プログラミングのテクニックを使ってフィルタリングを実現する方法を紹介します。読むには、Swift のジェネリックタイプと、ちょっとした関数型プログランミングの概念が必要です(といってもfiltersorterのような高階関数とクロージャが理解していれば大丈夫です)。

背景

自作アプリの「‎デレガイド2」は、アイドルリズムゲーム「アイドルマスター シンデレラガールズ スターライトステージ(デレステ)」の非公式アシスタントアプリで、その中で一番基本的な機能が、アイドルたちのカードを収録した図鑑機能です。

今2000枚以上のカードがアプリ内でローカルに保存されています。カードにはそれぞれのレア度、組1(キュート・クール・パッション)、特技などの属性を持っていて、ユーザーが任意の属性を指定してカードを絞り込む、つまりフィルタリングができます。

例えば、下図のよう、フィルタリングページで「SR+」「SSR+」と「キュート組」を指定すると、「レア度が SR+ あるいは SSR+、かつキュートに所属する」という条件に従うカードが絞り込まれます。

1.jpg

多くの属性の中で、「レア度・組」のように属性の数が一定するものもあって、「特技種類・特技の発動確率・間隔」のような、ゲーム本体の更新につれて増える可能性のあるものもあります。ゲーム更新に対して、自動的に、あるいは手動でも簡単で柔軟に対応できるために、フィルタリング機能をどう書けばいいんですか?

よくないやり方

既に気ついたかもしれないが、デレガイド2ってことは、バージョン1があることです。開発者は自分ではないが、昔のバージョンが同じ機能がありました。4年前のコードを見ると、全ての属性がハードコーディングされていて、OptionSetでフィルタリングを実現したようです。

OptionSet が知らない方には、とりあえず複数の値を持つEnumと考えても良いです。

例えば、特技のフィルタリング用の構造体がこのように書かれています:

struct CGSSSkillTypes: OptionSet {
    let rawValue: UInt
    init(rawValue: UInt) { self.rawValue = rawValue }

    static let comboBonus = CGSSSkillTypes(rawValue: 1 << 0)
    static let perfectBonus = CGSSSkillTypes(rawValue: 1 << 1)
    static let overload = CGSSSkillTypes(rawValue: 1 << 2)
    static let perfectLock = CGSSSkillTypes(rawValue: 1 << 3)
    //
    // ...すべての特技
    //
    static let all = CGSSSkillTypes(rawValue: 0b1111_1111_1111_1111_1111)
}

カード用フィルターと使い方:

struct CGSSCardFilter: CGSSFilter {

    var attributeTypes: CGSSAttributeTypes // 組
    var rarityTypes: CGSSRarityTypes
    var skillTypes: CGSSSkillTypes
    // ...いくつあります

    func filter(_ cards: [CGSSCard]) -> [CGSSCard] {
        return cards.filter { card in
            return cardTypes.contains(card.cardType) &&
            rarityTypes.contains(card.rarityType) &&
            attributeTypes.contains(v.attributeType) &&
            // ...
        }
    }
}

let resultCards = cardFilter.filter(allCards)

かなり直球的なデザインですね。このやり方にすると、いくつデメリットがあります:

  • 実際に使用したそれぞれの構造(Rarity, Skill など)と別として、ある程度同じ意味の持つフィルタリング用の構造体(RarityType, SkillType)を新設して、余剰性(redundancy)が持たされた。
  • アプリ内では、絞り込まれるものはカードだけではなく、曲とアイドルも同じ。違う実体に対して、またその配下の属性に、それぞれのコードを書く必要があり、コード量が異常に多い。
  • ゲーム本体の更新につれ新しい属性が追加されるたび、相応のOptionSetに新しい選択肢を手動で追加しなければならないし、allも変える必要がある。メンテナンスしにくい。

まったくエレガントではない!

今のやり方

基本定義

考えとしては、(概念上の)フィルターを、ハードコードで書き込むではなく、一つ一つの変数として扱うのほうが便利ではないですか。この関数を変数化するデザインが、まさに Swift のfilterがやっていることと同じです。

まず、フィルタータイプの関数クロージャをリネームします:

typealias Filter<T> = (T) -> Bool

ここから、このFilterをめぐって様々な機能を拡張します。

フィルタリング用のプロトコルを作ります:

protocol PropertyForFiltering {
    associatedtype Category

    let localized: String // id として区別用
    var filter: Filter<Category> { get }
}

意味は「このプロトコルに従うものがCategoryを絞り込むために使われる」です。filterは、「このプロトコルに従う属性と一致するようにCategoryオブジェクトを絞り込む」という関数クロージャです。

使い方としては、例えば、レア度が列挙型で定義されています:

enum Rarity: Int, CaseIterable {
    // ...
    case sr
    case srp
    case ssr
    case ssrp
}

RarityPropertyForFilteringに conform する。

extension Rarity: PropertyForFiltering {
    var filter: Filter<Card> {
        return { card in
            return card.rarity == self
        }
    }
}

こうすると、一つのケースが、一つの「レア度がそのケースと一致するカードを選ぶ」というフィルターを変数として作れます。使い方は:

ssrCards = allCards.filter(Rarity.ssr.filter)
// 以下と相当する
ssrCards = allCards.filter { card in
    return card.rarity == Rarity.ssr
}

そして、複数のフィルターを取り組んで使いたいので、まず汎用的な方法を書きます:

/// And(conjunction), return a new filter that satisfy two parameter filters.
func && <T> (f1: @escaping Filter<T>, f2: @escaping Filter<T>) -> Filter<T> {
    return { t in
        return f1(t) && f2(t)
    }
}

/// Or(disjunction), return a new filter that satisfy one of parameter filters as least.
func || <T> (f1: @escaping Filter<T>, f2: @escaping Filter<T>) -> Filter<T> {
    return { t in
        return f1(t) || f2(t)
    }
}

ここでは、Filterに対して&&論理AND演算子を再定義して、「二つのFilterとも満足する」という新しいFilterを作ることができます。||OR 演算子も同じ。
こうすると、あらゆるフィルターをいとも簡単に書けます:

// カードのレア度が SR あるいは SSR
let filter1 = Rarity.sr.filter || Rarity.ssr.filter

// レア度が SSR+、組がキュートあるいはクール、特技がスコアボーナス
// という条件に絞り込むフィルター
let filter2 =
    Rarity.ssrp.filter &&
    (Chara.Attribute.cute.filter || Chara.Attribute.cool.filter) &&
    scoreBonusSkill.filter

let filteredCard = allCard.filter(filter2)

整合

ここまで、基盤的なものが準備できました。次に多数の属性を一箇所に管理しよう、と思いましたが:

struct PropertySet<Category> {
    var properties: [PropertyForFiltering<Category>] = []
}
// ❌ Compile Error: Cannot specialize non-generic type 'PropertyForFiltering'

どうやら、少なくとも Swift 5.3 まで、Selfassociatedtype付きのProtocolが変数の型として直接に使えないので、Type Erasure の方法を使います:

struct AnyFilterToggle<Category>: PropertyForFiltering {

    let localized: String
    let filter: Filter<Category>
    var isOn = false

    init<Property: PropertyForFiltering>(property: Property) where Category == Property.Category {
        self.localized = property.localized
        self.filter = property.filter
    }

    init(localized: String, abbreviation: String? = nil, filter: @escaping Filter<Category>) {
        self.localized = localized
        self.filter = filter
    }
}

ここのAnyFilterToggleが、PropertyForFilteringに従う具体的な型を抹消して、すべての属性を同じ型として扱うことができるようにしました。AnyObjectや SwiftUI のAnyViewも同じパターンです。
(本来AnyPropertyForFilteringみたいな名前が適切ですけど、有効ブール値もここに入れたので、スウィッチ(Toggle)のほうに似ています)

// SR の Toggle
let srToggle = AnyFilterToggle(property: Rarity.sr)

// すべてのレア度の Toggle
let allRarityToggles = Rarity.allCases.map(AnyFilterToggle.init)

// 具体的属性変数がなくても、いろんなフィルターを柔軟に作れる
// Vocal能力値が5000以上のフィルター Toggle
let customToggle = AnyFilterToggle<Card>(localized: "Vocal is over 5000", filter: { card in card.vocal >= 5000 })

ここで、属性たちの関係を振り返ります:

2.jpg

一つの属性を Toggle にし、同じ種類の複数属性の Toggle を Set に格納されます:

struct FilterPropertySet<Category> {

    var filterToggles: [AnyFilterToggle<Category>]

    init?(toggles: [AnyFilterToggle<Category>]) { ... }

    init?<Property: PropertyForFiltering>(properties: [Property])
        where Property.Category == Category
    {
        // ...
        filterToggles = properties.map(AnyFilterToggle.init)
    }

    // Toggle 全部選ばれている、あるいは全部選ばれていない、という場合は無効と見なす
    var isAffectable: Bool {
        ! filterToggles.allSatisfy({$0.isOn}) &&
        ! filterToggles.allSatisfy({!$0.isOn})
    }

    // 有効した Toggle の filter を OR 関係で組み合って、Set レベルの filter を作る
    var filter: Filter<Category> {
        guard isAffectable else { return { _ in true } }
        var result: Filter<Category> = { _ in false }
        for toggle in filterToggles where toggle.isOn {
            result = result || toggle.filter
        }
        return result
    }
}

そして同じロジックで、複数の Set を Requirement に格納されて、filter を論理 AND 関係で組み合って 最終 filter を作ります。

struct FilterRequirement<Category> {
    var propertySets: [FilterPropertySet<Category>]

    // テスト:このコードを理解できますか?(上の filter とほぼ同じ意味)
    var finalFilter: Filter<Category> {
        propertySets
            .filter(\.isAffectable)
            .map(\.filter)
            .reduce({ _ in true }, &&)
    }
    // こういうコード書けるのが Swift の醍醐味ですよ!
}

これで準備万端。

使い方

let cardFilterRequirement = FilterRequirement<Card>(propertySets: [
    FilterPropertySet(properties: Rarity.allCases),
    FilterPropertySet(properties: allSkillCategory),
    FilterPropertySet(properties: Card.FetchMethod.allCases),
    // ...
])

let filteredCards = allCards.filter(cardFilterRequirement.finalFilter)

こうやって、複数の属性で FilterSet を作成して、そして複数の FilterSetFilterRequirement を作成します。Set の中の Toggle の isON をいじりながら、有効した Toggle から出たすべての filterFilterSetFilterRequirementfilter を通して 一つの filter にまとめます。

すべてのものが様々な属性変数から作れるので、特技種類みたいに、サーバーからのデータで生成されたものが、新しい特技が追加されても、自動的に変数としてallSkillCategoryに含まれていて、フィルターに変わるので、非常に便利です。

同じパターン

実際に、アイテムの属性だけではなく、ユーザーが検索欄で入力した文字列や:

func makeCardSearchFilter(searchText: String) -> Filter<Card> {
    return { card in
        card.name.contains(searchText)
    }
}

let searchFilter = makeCardSearchFilter(searchText: "卯月")
let filteredCard = allCards.filter(searchFilter && requirement.filter)

アイテムの並び替えも:

typealias Sorter<T> = (T, T) -> Bool

// protocol PropertyForSorting
// class AnyPropertyForSorting<Category>
// class SorterRequirement<Category>

let sortedCard = cards.sorted(cardSorterRequirement.finalSorter)

同じデザインで実装しています。

こういったアーキテクチャーで、すべてのカードを絞り込んで、並べ替えて展示します:

3.jpg

このやり方のメリット

新しい属性が追加された時、既存コードを変える必要がない

新しい属性の追加が容易にできる。

列挙型の属性が追加された時、caseを一行で書き加えるだけで;サーバーから生成された動的属性でしたら、コードを書く必要さえなく、自動的に対応する;たとえ新しい属性セットが入れたい時でも、新しいFilterPropertySetを書くだけで良い。既存なコードを変える必要が全くない。

柔軟性が高い、テストしやすい

変数ではなくでも、上のtextFilterのようなフィルターを直接書き出せるので、柔軟性が高く、様々なフィルターを自由に組み合わせる。

このように具体的なロジックを予め用意して、使う時には宣言的にフィルター作るだけで、コンパイラーに通されたらバグが出る可能性が低く、テストもしやすい。

コード量が少ない

実際に、カード・曲・アイドル、三つの実体に、それぞれの Filter と Sorter を実装しました。以前のやり方より圧倒的コード量が少ない。

おわりに

初投稿でちょうどいいテーマにしようと思って、書けば書くほど思った以上の量になりました。

日本語もバラバラですので、説明が不十分や、質問したいことがあれば気軽にコメントしてください!

Refer:


  1. 組:普通でしたら「キュート・クール・パッション」がカードの属性(Attribute)と呼びますが、本記事では Property という意味の「属性」を区別するため、「組」と呼びます。 

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

Dart vs Swift

| 作者 : Andrea Bizzotto
| 原文 : medium
| 翻訳 : jamestong
| 校正 : jamestong

DartとSwiftは私のお気に入りの2つのプログラミング言語です。私は商用およびオープンソースのコードでこれらを幅広く使用してきました。

この記事では、DartとSwiftを並べて比較してみます。

  • 両者の違いを強調する;
  • 開発者の参考として、一方の言語から他方の言語に移行する(または両方を使用する)時の注意点を挙げる。

背景:

  • Dartは、単一のコードベースから美しいネイティブアプリを構築するためのGoogleのフレームワークであるFlutterに対応している;
  • Swiftは、iOS、macOS、tvOS、watchOSにまたがるAppleのSDKをサポートしている。

以下は、両言語の主な機能(Dart 2.1Swift 4.2の時点で)を比較したものです。各機能の詳細な議論はこの記事の範囲を超えているので、必要に応じて両言語の参考文献を参考してください。

目次

  • 対照表
  • 変数
  • 型推論
  • 可変型/不可変型変数
  • 関数
  • 名前付きパラメータと名前なしパラメータ
  • オプションとデフォルトのパラメータ
  • クロージャ
  • タプル
  • 制御フロー
  • コレクション(arrays, sets, maps)
  • Nullability & Optionals
  • クラス
  • 継承
  • プロパティー
  • プロトコル / 抽象クラス
  • Mixins
  • 拡張
  • 列挙型
  • 構造体
  • エラー処理
  • ジェネリック
  • アクセス制御
  • 非同期プログラミング:Futures
  • 非同期プログラミング:Streams
  • メモリ管理
  • コンパイルと実行
  • その他の機能
  • Dartに欠けている私のお気に入りのSwiftの機能
  • Swiftに欠けている私のお気に入りのDartの機能
  • 結論

対照表

1.png

変数

変数宣言の構文は、Dartでは次のように:

String name;
int age;
double height;

Swiftではこのように:

var name: String
var age: Int
var height: Double

変数の初期化はDartではこのように:

var name = 'Andrea';
var age = 34;
var height = 1.84;

Swiftではこのように:

var name = "Andrea"
var age = 34
var height = 1.84

上記らの例では、型のアノテーションは必要ありません。これは、どちらの言語も代入の右側の式から型を推測できるからです。

型推論

型推論とは、Dartで次のように書けるということです。

var arguments = {'argA': 'hello', 'argB': 42}; // Map<String, Object>

そして、argumentsの型はコンパイラによって自動的に解決されます。

Swiftでは、同じように書くことができます:

var arguments = [ "argA": "hello", "argB": 42 ] // [ String : Any ]

さらに

Dartのドキュメントを引用します。

解析器は、フィールド、メソッド、ローカル変数、およびほとんどのジェネリック引数の型を推論することができます。解析器が特定の型を推論するのに十分な情報を持っていない場合は、動的型付けdynamicを使います。

そしてSwiftの場合は:

Swiftは広範囲に型推論を使用しており、コード中の多くの変数や式の型や型の一部を省略することができます。例えば、var x: Int = 0のように書くことではなく, var x = 0のように書けば、 コンパイラは x が Int 型の値を指定していることを正しく推論します。

動的型付け

任意の型を持つ変数は、Dartではdynamicキーワード、SwiftではAnyキーワードで宣言されます。

動的型付けはJSONなどのデータを読み込むときによく使われます。

可変型/不変型変数

変数は、可変なものと不変なものを宣言することができます。

可変な変数を宣言するには、両言語とも var キーワードを使用します。

var a = 10; // int (Dart)
a = 20; // ok
var a = 10 // Int (Swift)
a = 20 // ok

不変変数の宣言には、Dartではfinal、Swiftではletを使います。

final a = 10;
a = 20; // 'a': a final variable, can only be set once.
let a = 10
a = 20 // Cannot assign to value: 'a' is a 'let' constant

注意:Dartのドキュメントでは、finalconstという2つのキーワードが定義されていますが、これらは以下のように動作します。

変数を変更するつもりがない場合は、varの代わりに、または型に加えて、finalまたはconstを使用してください。final変数は一度だけ設定することができ、const変数はコンパイル時の定数です。(const変数は暗黙のうちにfinalになります。) finalのトップレベル変数やクラス変数は、初めて使用されたときに初期化されます。

さらに

finalはシングルアサインメントを意味します。最終的な変数やフィールドにはinitializerが必要です。一度値が代入されると、final変数の値を変更することはできません。

TL;DR: Dartで不変変数を定義するにはfinalを使います。

Swiftではletで定数を宣言します。

定数の宣言は、定数の値をプログラムに導入します。定数はletキーワードを使って宣言され、以下のような形式になります。

let constant name: type = expression

定数の宣言は、定数名とイニシャライザ式の値との間の不変の結合を定義します。
定数の値が設定された後は変更できません。

関数

関数はSwiftとDartの中では一等市民です。

これはオブジェクトと同じように、関数は引数として渡されたり、プロパティとして保存されたり、結果として返されたりすることができることを意味します。

最初の比較として、引数のない関数の宣言方法を見てみましょう。

Dartでは、メソッド名の前にリターンタイプが付きます。

void foo();
int bar();

Swift では、サフィックスとして -> T 記法を使用しています。戻り値がない場合(Void)にはそれを付く必要がありません。

func foo()
func bar() -> Int

名前付きパラメータと名前なしパラメータ

どちらの言語も名前付きと名前なしのパラメータをサポートしています。

Swiftでは、パラメータはデフォルトで名前が付けられています。

func foo(name: String, age: Int, height: Double)
foo(name: "Andrea", age: 34, height: 1.84)

Dartでは、中括弧({ })で名前付きパラメータを定義します。

void foo({String name, int age, double height});
foo(name: 'Andrea', age: 34, height: 1.84);

Swiftでは、外部パラメータとしてアンダースコア(_)を使って名前のないパラメータを定義します。

func foo(_ name: String, _ age: Int, _ height: Double)
foo("Andrea", 34, 1.84)

Dartでは、中括弧({ })を省略して名前のないパラメータを定義します。

void foo(String name, int age, double height);
foo('Andrea', 34, 1.84);

オプションとデフォルトのパラメータ

どちらの言語もデフォルトのパラメータをサポートしています。

Swiftでは、パラメータの型の後にパラメータに値を割り当てることで、関数内の任意のパラメータのデフォルト値を定義することができます。デフォルト値が定義されている場合、関数を呼び出す際にそのパラメータを省略することができます。

func foo(name: String, age: Int = 0, height: Double = 0.0) 
foo(name: "Andrea", age: 34) // name: "Andrea", age: 34, height: 0.0

Dartでは、オプションのパラメータは位置指定か名前指定のどちらかを指定することができますが、両方を指定することはできません。

// positional optional parameters
void foo(String name, [int age = 0, double height = 0.0]);
foo('Andrea', 34); // name: 'Andrea', age: 34, height: 0.0
// named optional parameters
void foo({String name, int age = 0, double height = 0.0});
foo(name: 'Andrea', age: 34); // name: 'Andrea', age: 34, height: 0.0

クロージャ

一級オブジェクトである関数は、他の関数の引数として渡されたり、変数に代入されたりすることができます。

この文脈では、関数はクロージャclosureとしても呼ばれています。

ここでは、各項目のインデックスと内容を表示するためにクロージャを使用して、項目のリストを反復処理する関数のDartの例を示します。

final list = ['apples', 'bananas', 'oranges'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));

クロージャは1つの引数(item)を取り、そのitemのインデックスと値を表示するが、リターンしません。

矢印表記 (=>) を使用していることに注意してください。これは中括弧の中にある単一のreturn文の代わりに使うことができます。

list.forEach((item) { print('${list.indexOf(item)}: $item'); });

Swiftでは同じコードは次のようになります。

let list = ["apples", "bananas", "oranges"]
list.forEach({print("\(String(describing: list.firstIndex(of: $0))) \($0)")})

この場合、クロージャに渡される引数の名前は指定せず、代わりに $0 を使用して最初の引数を意味します。これは完全にオプションであり、必要に応じて名前付きのパラメータを使用することもできます。

list.forEach({ item in print("\(String(describing: list.firstIndex(of: item))) \(item)")})

Swiftでは非同期コードの補完ブロックとしてクロージャがよく使われます。

タプル

Swiftドキュメントより:

タプルは複数の値を1つの複合値にまとめます。タプル内の値は任意の型をとることができ、互いに同じ型である必要はありません。

これらは小型の軽量型として使用することができ、複数の戻り値を持つ関数を定義する際に便利です。

Swiftでのタプルの使い方:

let t = ("Andrea", 34, 1.84)
print(t.0) // prints "Andrea"
print(t.1) // prints 34
print(t.2) // prints 1.84

Dartではサードパーティパッケージでタプルを実現出来ます。

const t = const Tuple3<String, int, double>('Andrea', 34, 1.84);
print(t.item1); // prints 'Andrea'
print(t.item2); // prints 34
print(t.item3); // prints 1.84

制御フロー

どちらの言語も様々な制御フロー文を提供しています。

例としては、if 条件文、for ループ、while ループ、switch文などがあります。

これらをここで説明するとかなり長くなるので、公式ドキュメントを参照してください。

コレクション(arrays, sets, maps)

Arrays / Lists

配列はオブジェクトの順序付けされたグループです。

DartではListで配列Arraysを作ることができます。

var emptyList = <int>[]; // empty list
var list = [1, 2, 3]; // list literal
list.length; // 3
list[1]; // 2

Swiftでの配列は組み込み型です。

var emptyArray = [Int]() // empty array
var array = [1, 2, 3] // array literal
array.count // 3
array[1] // 2

Sets

Swiftのドキュメントより:

Setは、定義された順序を持たないコレクションの中に、同じ型の異なる値を格納します。アイテムの順序が重要ではない場合や、アイテムが一度しか表示されないようにする必要がある場合には、配列の代わりにセットを使うことができます。

DartのSet クラスはこのように定義されています。

var emptyFruits = <String>{}; // empty set literal
var fruits = {'apple', 'banana'}; // set literal

同様に、Swiftは:

var emptyFruits = Set<String>()
var fruits = Set<String>(["apple", "banana"])

Maps / Dictionaries

Swiftのドキュメントには、map/dictionaryに対し良い定義があります。

辞書は、同じ型のキーと同じ型の値の間の関連付けを、定義された順序を持たないコレクションに格納します。各値は唯一のキーに関連付けられ、辞書内でその値の識別子として機能します。

Dartはmapを次のように定義しています。

var namesOfIntegers = Map<Int,String>(); // empty map
var airports = { 'YYZ': 'Toronto Pearson', 'DUB': 'Dublin' }; // map literal

Swiftではmapを辞書dictionaryと呼びます。

var namesOfIntegers = [Int: String]() // empty dictionary
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] // dictionary literal

Nullability & Optionals

Dartでは、どんなオブジェクトでもnullにすることができます。そして、null オブジェクトのメソッドや変数にアクセスしようとすると、nullポインタ例外が発生します。これは、コンピュータプログラムで最も一般的なエラーの原因の一つです(最も一般的ではないにしても)。

最初から、Swiftにはオブジェクトが値を持てるか持てないかを宣言するための組み込みの言語機能であるオプショナルがありました。ドキュメントを引用します。

値が存在しないかもしれない状況では、オプショナルOptionalを使用します。オプショナルは2つの可能性を表します。値があり、その値にアクセスするためにオプションをアンラップすることができる場合と、値が全く存在しない場合です。

これとは対照的に、非オプショナル変数を使用することで、常に値を持つことを保証できます。

var x: Int? // optional
var y: Int = 1 // non-optional, must be initialized

ノート:Swiftの変数がオプショナルであると言うことは、Dartの変数がnullになる可能性があると言うこととほぼ同じです。

言語レベルでオプショナルをサポートしていない場合、変数がnullであるかどうかを実行時にチェックすることしかできません。

オプショナルを使うと、コンパイル時に情報をエンコードします。オプショナルをアンラップすることで、値を保持しているかどうかを安全にチェックすることができます。

func showOptional(x: Int?) {
  // use `guard let` rather than `if let` as best practice
  if let x = x { // unwrap optional
    print(x)
  } else {
    print("no value")
  }
}
showOptional(x: nil) // prints "no value"
showOptional(x: 5) // prints "5"

そして、変数が値を持たなければならないことがわかっている場合は、非オプショナルnon-optionalを使うことができます。

func showNonOptional(x: Int) {
  print(x)
}
showNonOptional(x: nil) // [compile error] Nil is not compatible with expected argument type 'Int'
showNonOptional(x: 5) // prints "5"

上の最初の例は、Dartではこのように実装することができます。

void showOptional(int x) {
  if (x != null) {
    print(x);
  } else {
    print('no value');
  }
}
showOptional(null) // prints "no value"
showOptional(5) // prints "5"

そして2個目はこんな感じ:

void showNonOptional(int x) {
  assert(x != null);
  print(x);     
}
showNonOptional(null) // [runtime error] Uncaught exception: Assertion failed
showNonOptional(5) // prints "5"

オプショナルを持つことは、実行時ではなくコンパイル時にエラーをキャッチできることを意味します。そして、エラーを早期にキャッチすることで、より安全でバグの少ないコードを作ることができます。

Dart はオプショナルをサポートしていませんが、アサーション (および名前付きパラメータの @required アノテーション) を使用することでなんとか緩和されています。

これらのアサーションは Flutter SDK で広く使われていますが、結果的には、余計の定型的なコードが生じます。

クラス

クラスはオブジェクト指向言語でプログラムを書くための主要な構成要素です。

クラスは Dart と Swiftが両方ともサポートしていますが、いくつかの違いがあります。

構文

Swiftのinitializerと3つのメンバ変数を持つクラスです。

class Person {
  let name: String
  let age: Int
  let height: Double
  init(name: String, age: Int, height: Double) {
    self.name = name
    self.age = age
    self.height = height
  }
}

Dartも同じように:

class Person {
  Person({this.name, this.age, this.height});
  final String name;
  final int age;
  final double height;
}

Dart のコンストラクタで this.[propertyName] を使用することに注意してください。これは、コンストラクタが実行される前にインスタンスのメンバ変数を設定するための糖衣構文syntactic sugarです。

ファクトリ コンストラクタ

Dartでは、ファクトリ・コンストラクタを作成することができます。

常にクラスの新しいインスタンスを生成しないコンストラクタを実装する場合は factory キーワードを使用します。

ファクトリコンストラクタの実用的な使用例としては、JSONからモデルクラスを作成する場合に使われます。

class Person {
  Person({this.name, this.age, this.height});
  final String name;
  final int age;
  final double height;
  factory Person.fromJSON(Map<dynamic, dynamic> json) {
    String name = json['name'];
    int age = json['age'];
    double height = json['height'];
    return Person(name: name, age: age, height: height);
  }
}
var p = Person.fromJSON({
  'name': 'Andrea',
  'age': 34,
  'height': 1.84,
});

継承

Swiftはシングル継承モデルを使用しており、どのクラスも1つのスーパークラスしか持っていないことを意味します。Swiftのクラスは複数のインターフェイス(プロトコルとも呼ばれる)を実装することができます。

Dartクラスはmixinベースの継承を持っています。ドキュメントを引用します。

すべてのオブジェクトは1つのクラスのインスタンスであり、すべてのクラスはObjectから派生します。mixinベースの継承とは、(Objectを除く)すべてのクラスにはちょうど1つのスーパークラスがありますが、クラス本体は複数のクラス階層で再利用できることを意味します。

Swiftでのシングル継承の動作:

class Vehicle {
  let wheelCount: Int
  init(wheelCount: Int) {
    self.wheelCount = wheelCount
  }
}
class Bicycle: Vehicle {
  init() {
    super.init(wheelCount: 2)
  }
}

Dartは:

class Vehicle {
  Vehicle({this.wheelCount});
  final int wheelCount;
}
class Bicycle extends Vehicle {
  Bicycle() : super(wheelCount: 2);
}

プロパティー

これらはDartではインスタンス変数と呼ばれ、Swiftでは単にプロパティと呼ばれます。

Swiftでは、保存されたプロパティと計算されたプロパティに区別があります。

class Circle {
  init(radius: Double) {
    self.radius = radius
  }
  let radius: Double // stored property
  var diameter: Double { // read-only computed property
    return radius * 2.0
  }
}

Dartでは、同じような区別があります:

class Circle {
  Circle({this.radius});
  final double radius; // stored property
  double get diameter => radius * 2.0; // computed property
}

計算されたプロパティのgettersに加えて、settersを定義することもできます。

上の例を使って、diameterプロパティをsetterを含むように書き換えることができます。

var diameter: Double { // computed property
  get {
    return radius * 2.0
  }
  set {
    radius = newValue / 2.0
  }
}

Dartでは、このように単独のsetterを追加することができます。

set diameter(double value) => radius = value / 2.0;

プロパティオブザーバ

これがSwiftの特徴です。

プロパティオブザーバーはプロパティの値の変化を観測し、それに反応します。プロパティオブザーバーは、プロパティの値が設定されるたびに、新しい値がプロパティの現在の値と同じであっても呼び出されます。

使い方:

var diameter: Double { // read-only computed property
  willSet(newDiameter) {
    print("old value: \(diameter), new value: \(newDiameter)")  
  }
  didSet {
    print("old value: \(oldValue), new value: \(diameter)")  
  }
}

プロトコル / 抽象クラス

ここでは、どのように実装されているかを指定せずに、メソッドやプロパティを定義するために使用される構文について説明します。これは他の言語ではインターフェースとして知られています。

Swiftでは、インターフェースをプロトコルと呼ばれています。

protocol Shape {
  func area() -> Double
}
class Square: Shape {
  let side: Double
  init(side: Double) {
    self.side = side
  }
  func area() -> Double {
    return side * side
  }
}

Dartには、抽象クラスとして知られる同様の構成があります。抽象クラスはインスタンス化できません。しかし、実装を持つメソッドを定義することはできます。

上の例は、Dartではこのように書くことができます。

abstract class Shape {
  double area();
}
class Square extends Shape {
  Square({this.side});
  final double side;
  double area() => side * side;
}

Mixins

Dartでは、Mixinは通常のクラスであり、複数のクラス階層で再利用することができます。

先ほど定義したPersonクラスをNameExtension mixinで拡張する方法を紹介します。

abstract class NameExtension {
  String get name;
  String get uppercaseName => name.toUpperCase();
  String get lowercaseName => name.toLowerCase();
}
class Person with NameExtension {
  Person({this.name, this.age, this.height});
  final String name;
  final int age;
  final double height;  
}
var person = Person(name: 'Andrea', age: 34, height: 1.84);
print(person.uppercaseName); // 'ANDREA'

拡張

拡張機能はSwift言語の1つの特徴です。ドキュメントを引用します。

拡張機能は既存のクラス、構造体、列挙、またはプロトコルの型に新しい機能を追加します。これには、元のソースコードにアクセスできない型を拡張する機能が含まれます(レトロアクティブモデリングretroactive modelingとして知られています)。

これは、Dart の mixins ではできません。

上の例を借りて、Person クラスを次のように拡張します。

extension Person {
  var uppercaseName: String {
    return name.uppercased()
  }
  var lowercaseName: String {
    return name.lowercased()
  }
}
var person = Person(name: "Andrea", age: 34, height: 1.84)
print(person.uppercaseName) // "ANDREA"

拡張機能にはここで紹介した以上に多くがあり、特にプロトコルやジェネリックと組み合わせて使用される場合には、それ以上に多くのことができます。

拡張機能の最も一般的な使用例の一つは、既存の型にプロトコルに準拠した機能を追加することです。例えば、既存のモデルクラスにシリアライズ機能を追加するために拡張機能を使用することができます。

列挙型

Dartは、列挙型に対しいくつかの基本的なサポートを持っています。

Swiftの列挙型は、関連する型をサポートしているので、非常に強力です。

enum NetworkResponse {
  case success(body: Data) 
  case failure(error: Error)
}

これにより、このようなロジックを書くことが可能になります。

switch (response) {
  case .success(let data):
    // do something with (non-optional) data
  case .failure(let error):
    // do something with (non-optional) error
}

dataパラメータとerrorパラメータが相互に排他的であることに注意してください。

Dartでは列挙型に追加の値を関連付けることはできませんので、上記のコードは次のように実装できるかもしれません。

class NetworkResponse {
  NetworkResponse({this.data, this.error})
  // assertion to make data and error mutually exclusive
  : assert(data != null && error == null || data == null && error != null);
  final Uint8List data;
  final String error;
}
var response = NetworkResponse(data: Uint8List(0), error: null);
if (response.data != null) {
  // use data
} else {
  // use error
}

いくつか注意点があります。

  • ここではアサーションを使用して、オプショナルがないという事実を補っています。
  • コンパイラはすべてのケースをチェックすることはできません。これはレスポンスを処理するプロセスでswitchを使わないからです。

まとめると、Swiftの列挙型はDartに比べてかなり強力で表現力があります。

Dart Sealed Unionsのようなサードパーティのライブラリは、Swift列挙型と同様の機能を提供し、ギャップを埋めるのに役立ちます。

構造体

Swiftでは構造体とクラスを定義することができます。

どちらの構造にも多くの共通点があり、いくつかの違いがあります。

主な違いは:

クラスは参照型であり、構造体は値型である。

ドキュメントを引用します。

値型とは、変数や定数に代入されたとき、または関数に渡されたときに値がコピーされる型のことです。

Swiftでは、構造体と列挙はすべて値型です。これは、作成した構造体と列挙のインスタンス、およびそれらがプロパティとして持つ値型は、コード内で渡されるときに常にコピーされることを意味します。

値型とは異なり、参照型は変数や定数に代入されたときや関数に渡されたときにはコピーされません。コピーではなく、同じ既存のインスタンスへの参照が使用されます。

これが何を意味するのかを知るために、次の例を考えてみましょう。

class Person {
  var name: String
  var age: Int
  var height: Double
  init(name: String, age: Int, height: Double) {
    self.name = name
    self.age = age
    self.height = height
  }
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 35

Personを構造体structとして定義し直すと、こんな感じになります。

struct Person {
  var name: String
  var age: Int
  var height: Double
  init(name: String, age: Int, height: Double) {
    self.name = name
    self.age = age
    self.height = height
  }
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 34

構造体には、ここで取り上げた以外にも多くのことがあります。
構造体は、Swiftでデータやモデルを扱うために効果的に使うことができ、バグの少ないロバストなコードrobust codeにつながります。

エラー処理

Swiftのドキュメントからの定義:

エラー処理とは、プログラムのエラー状態に応答して回復するプロセスです。

DartもSwiftもエラーを処理するためのテクニックとしてtry/catchを使用していますが、いくつかの違いがあります。

Dartでは、任意のメソッドは任意の型の例外を投げることができます。

class BankAccount {
  BankAccount({this.balance});
  double balance;
  void withdraw(double amount) {
    if (amount > balance) {
      throw Exception('Insufficient funds');
    }
    balance -= amount;
  }
}

例外はtry/catchブロックでキャッチできます。

var account = BankAccount(balance: 100);
try {
  account.withdraw(50); // ok
  account.withdraw(200); // throws
} catch (e) {
  print(e); // prints 'Exception: Insufficient funds'
}

Swiftでは、メソッドが例外を投げることができるときに明示的に宣言します。これはthrowsキーワードで行われ、全てのエラーはエラープロトコルに準拠しなければなりません。

enum AccountError: Error {
  case insufficientFunds
}
class BankAccount {
  var balance: Double
  init(balance: Double) {
    self.balance = balance
  }
  func withdraw(amount: Double) throws {
    if amount > balance {
      throw AccountError.insufficientFunds
    }
    balance -= amount
  }
}

エラーを処理する際には、do/catchブロックの中でtryキーワードを使用します。

var account = BankAccount(balance: 100)
do {
  try account.withdraw(amount: 50) // ok
  try account.withdraw(amount: 200) // throws
} catch AccountError.insufficientFunds {
  print("Insufficient Funds")
}

throwできるメソッドを呼び出す際には、tryキーワードが必須であることに注意してください。
また、エラー自体は強く型付けされているので、すべての可能なケースをカバーするために複数のキャッチブロックを持つことができます。

try, try?, try!

Swiftでは、エラーを扱うためにあまり冗長ではない方法を提供しています。

do/catch ブロックを使わずに try? を使用し、これにより全ての例外は無視されるようになります。

var account = BankAccount(balance: 100)
try? account.withdraw(amount: 50) // ok
try? account.withdraw(amount: 200) // fails silently

あるいは、あるメソッドはエラーが投げられないことが確実な場合は、try!を使用できます。

var account = BankAccount(balance: 100)
try! account.withdraw(amount: 50) // ok
try! account.withdraw(amount: 200) // crash

上の例では、プログラムがクラッシュしてしまいます。したがって、try! はプロダクションコードでは推奨されず、テストを書くときに適しています。

全体的に、Swiftにおけるエラー処理の明示的な性質はAPI設計において非常に有益です。メソッドがエラーを投げることができるかどうかを簡単に確認できるからです。

同様に、メソッド呼び出しで try を使用すると、投げることができるコードに注意が向けられ、エラーケースを考慮することを余儀なくされます。

この点(エラー処理)では、Swift は Dart よりも安全で堅牢なものになっています。

ジェネリック

Swiftのドキュメントを引用します。

ジェネリックコードを使うと、定義した要件に従って、どんな型でも動作する柔軟で再利用可能な関数や型を書くことができます。重複を避け、明確で抽象的な方法で意図を表現するコードを書くことができます。

ジェネリックについて両言語ともサポートしています。

ジェネリックの最も一般的な使用例の1つは、配列、集合、マップなどのコレクションです。

そして、これらを使って独自の型を定義することができます。SwiftでジェネリックStack型を定義する方法を以下に示します。

struct Stack<Element> {
  var items = [Element]()
  mutating func push(_ item: Element) {
    items.append(item)
  }
  mutating func pop() -> Element {
    return items.removeLast()
  }
}

同様に、Dartではこう書きます。

class Stack<Element> {
  var items = <Element>[]
  void push(Element item) {
    items.add(item)
  }
  void pop() -> Element {
    return items.removeLast()
  }
}

Swiftのジェネリックでは非常に便利で強力なもので、プロトコルの型制約や関連する型を定義するために使用することができます。

アクセス制御

Swiftのドキュメントを引用します。

アクセス制御は、他のソースファイルやモジュールのコードからコードの一部へのアクセスを制限します。この機能により、コードの実装の詳細を隠したり、そのコードにアクセスして使用できる優先インターフェースを指定したりすることができます。

Swiftには5つのアクセスレベルがあります: openpublicinternalfile-privateprivateです。

これらはモジュールやソースファイルを扱う際に使用されます。

モジュールとは、コード配布の単一ユニット、つまりフレームワークやアプリケーションを構築して単一ユニットとして出荷され、Swiftのimportキーワードで別のモジュールからインポートできるものです。

openpublicアクセスのレベルは、モジュールの外部からコードにアクセスできるます。

privateおよびfile-privateのアクセスレベルは、定義されているファイルの外ではコードにアクセスできません。

例:

public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}

アクセスレベルはDartの方がシンプルで、publicprivateに限定されています。

Javaとは異なり、Dartにはpublicprotectedprivateというキーワードはありません。識別子がアンダースコア_で始まる場合は、それはプライベートです。

例:

class HomePage extends StatefulWidget { // public
  @override
  _HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> { ... } // private

アクセス制御は、DartとSwiftでは異なる目的で設計されました。その結果、アクセスレベルが大きく異なっています。

非同期プログラミング:Futures

非同期プログラミングはDartが最も得意とする分野です。
以下のようなユースケースを扱う場合、何らかの形の非同期プログラミングが必要になります。

  • ウェブからコンテンツをダウンロードする
  • バックエンドとの通信
  • 長時間稼働する業務を行う

これらのケースでは、実行中のメインスレッドをブロックしない方が良いでしょう。

Dartのドキュメントを引用します。

非同期操作は、操作が終わるのを待っている間に他の作業を完了させることができます。Dartでは、非同期操作の結果を表現するためにFutureオブジェクトを使用します。Futureを使用するには、async/awaitまたはFuture APIを使用します。

例として、非同期プログラミングをどのように使うかを見てみましょう。

  • サーバでユーザを認証する
  • アクセストークンを格納してストレージを守る
  • ユーザープロファイル情報を取得する

Dartでは、これはFutureasync/awaitの組み合わせで実現できます。

Future<UserProfile> getUserProfile(UserCredentials credentials) async {
  final accessToken = await networkService.signIn(credentials);
  await secureStorage.storeToken(accessToken, forUserCredentials: credentials);
  return await networkService.getProfile(accessToken);
}

Swiftでは、async/awaitというAPIを提供しておらず、クロージャ(補完ブロック)でしか実現できません。

func getUserProfile(credentials: UserCredentials, completion: (_ result: UserProfile) -> Void) {
  networkService.signIn(credentials) { accessToken in
    secureStorage.storeToken(accessToken) {
      networkService.getProfile(accessToken, completion: completion)
    }
  }
}

これは、ネストされた補完(completion)ブロックによる「運命のピラミッド(pyramid of doom)」につながります。そして、このシナリオではエラー処理が非常に難しくなります。

Dartでは、上記コードのエラーの処理は、getUserProfileメソッドにtry/catchブロックを追加するだけで済みます。

参考として、将来的にはSwiftにasync/awaitを追加するという提案があります。

これが実装されるまでは、開発者はGoogleのPromisesのようなサードパーティ製のライブラリを使うことができます。

非同期プログラミング:Streams

StreamsはDartのコアライブラリの一部として実装されていますが、Swiftでは実装されていません。

Dartのドキュメントを引用します。

Streamは非同期イベントのシーケンスです。

Streamはリアクティブなアプリケーションの基本であり、状態管理で重要な役割を果たします。

例えば、ユーザーが検索フィールドのテキストを更新するたびに、新しい結果のセットが出力されます。

StreamはSwiftのコアライブラリには含まれていません。RxSwiftのようなサードパーティのライブラリはStreamに対するサポートやその他多くの機能を提供しています。

ストリーム(Stream)は幅広いトピックなので、ここでは詳しく説明しません。

メモリ管理

Dartは高度なガベージコレクション(garbage collection)方式でメモリを管理します。

Swiftは自動参照カウント(ARC)でメモリを管理します。

これは、メモリが使われなくなるとすぐに解放されるので、優れたパフォーマンスを保証します。

しかし、コンパイラから開発者に負担を部分的にシフトしています。

Swiftでは、保持サイクルを回避するためにオブジェクトのライフサイクルと所有権について考慮し、適切なキーワード(weak,strong,unowned)を正しく使う必要があります。

コンパイルと実行

まず最初に、ジャストインタイムjust-in-time(JIT)コンパイラと事前ahead-of-time(AOT)コンパイラの重要な違いを説明します。

JITコンパイラ

JIT コンパイラはプログラムの実行中に実行され、その場でコンパイルを行います。

JIT コンパイラは、型が事前に固定されていない動的な言語で使用されるのが一般的です。JITプログラムはインタプリタや仮想マシン(VM)を介して実行されます。

AOTコンパイラ

AOT コンパイラはプログラムの作成時に実行前に実行されます。

AOT コンパイラは通常、データの型を知っている静的言語で使用されます。AOTプログラムはネイティブのマシンコードにコンパイルされ、実行時にハードウェアによって直接実行されます。

Wm Leler氏のこの素晴らしい記事を引用します。

AOTコンパイルが開発中に行われる場合、開発サイクル(プログラムに変更を加えてからその変更の結果を見るためにプログラムを実行できるようになるまでの時間)が大幅に遅くなります。しかし、AOT コンパイルは、実行時に解析やコンパイルのために一時停止することなく、より予測可能なプログラムを実行することができます。また、AOTコンパイルされたプログラムは、(すでにコンパイルされているため)より速く実行を開始します。

逆に、JITコンパイルは開発サイクルを大幅に短縮しますが、実行が遅くなったり、不安定になったりします。特に、JIT コンパイラはプログラムの実行を開始すると、コードを実行する前に解析とコンパイルを行わなければならないため、起動時間が遅くなります。研究によると、実行開始までに数秒以上かかると、多くの人がアプリを放棄してしまうという結果が出ています。

静的言語として、Swiftは事前にコンパイルされます

DartはAOTJITの両方でコンパイルできます。これはFlutterと併用した場合に大きなメリットがあります。

再度引用します。

開発時には、特に高速なコンパイラを使ってJITコンパイルを行います。そして、アプリのリリース準備が整ったら、AOTコンパイルを行います。その結果、先進的なツールとコンパイラの力を借りて、Dartは、開発サイクルの超高速化と、実行時間及び起動時間の高速化という両世界のベストを実現することができます。-Wm Leler

Dartを使うと、両世界の最高のものを手に入れることができます。

SwiftはAOTコンパイルに主な欠点があります。つまり、コンパイル時間はコードベースのサイズに応じて増加します。

中規模のアプリ(10Kから100K行)では、アプリのコンパイルに数分かかることがあります。

Flutter アプリの場合はそうではなく、コードベースのサイズに関係なく、常に一秒以内のホットリロード(hot-reload)ができます。

その他の機能

以下の機能は、DartとSwiftはかなり似ているので取り上げませんでした。

  • 演算子
  • 文字列
  • SwiftではオプショナルチェイニングOptional chaining(Dartでは条件付きメンバーアクセスとして知られています)

並行性

  • 同時で並行プログラミングはDartのisolateで提供されています。
  • SwiftはGrand Central Dispatch (GCD)とディスパッチキュー(dispatch queue)を使用します。

Dartに欠けている私のお気に入りのSwiftの機能

  • 構造体
  • 関連する型を持つ列挙型
  • オプションナル

Swiftに欠けている私のお気に入りのDartの機能

  • JITコンパイラ
  • Future と await/async
  • Stream と yield/async*(RxSwiftはリアクティブアプリケーション用のストリーム(Stream)のスーパーセットを提供しています)

結論

DartとSwiftはどちらも優れた言語であり、現代のモバイルアプリやそれ以上のものを構築するのに適しています。

どちらの言語も独自の長所を持っているので、どちらが優れているということはありません。

モバイルアプリの開発と2つの言語のツールを見ると、私はDartが優位に立っていると感じます。これは、Flutter のステートフルホットリロードstateful hot-reloadの基盤となっている JIT コンパイラによるものです。

ホットリロードはアプリを構築する際に生産性を大きく向上させます。

開発者の時間は、コンピューティングの時間よりも希少なリソースです。

そのため、開発者の時間を最適化することは非常にスマートな動きです。

一方で、Swiftは非常に強力な型システムを持っていると感じています。型の安全性はすべての言語機能に組み込まれており、より自然にロバスト(頑健)なプログラムにつながります。

個人的な好みはさておき、プログラミング言語は単なるツールに過ぎません。そして、その仕事に最も適したツールを選ぶのが開発者としての私たちの仕事です。

いずれにしても、どちらの言語もお互いに最高のアイデアを借りながら進化していくことを期待しています。

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

【Swift】バックグラウンド的な処理をタイマーで実装してみた

どうも、ねこきち(@nekokichi1_yos2)です。

Swiftにはバックグラウンド処理を実装する方法はありますが、
特定の用途に限定
汎用的な方法もあるが短時間
長時間の方法はAppleが推奨してない
処理が安定しない
の理由で扱いが難しいです。

しかし、簡単な処理ならば、バックグラウンド処理のメソッドを使用しなくても、バックグラウンドは実現できます。

そこで、デリゲートを使って、バックグラウンドに対応したタイマーを実装します。

解説

実装する機能は、
・タイマー
・バックグラウンドとのやりとり
・デリゲート
の3つ。

処理の流れは、下記の通り。
1. ボタン押下
2. タイマー起動
3. バックグラウンドに移行
4. アプリ画面に復帰
5. バックグラウンドでの経過時間を残り時間から引く
6. タイマー終了

使用するのは、
- backgroundTimer.swift
- SceneDelegate.swift
のファイルです。

タイマーの設定

まずは、タイマーをちょちょいと。

backgroundTimer.swift
import UIKit

class backgroundTimer: UIViewController {

    @IBOutlet weak var currentTimeLabel: UILabel!
    @IBOutlet weak var start: UIButton!
    //タイマー
    var timer:Timer!
    //残り時間
    var currentTime = 15

    override func viewDidLoad() {
        super.viewDidLoad()
        currentTimeLabel.text = "15"
    }

    @IBAction func start(_ sender: Any) {
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(advancedTime), userInfo: nil, repeats: true)
        start.isHidden = true
    }

    @objc func advancedTime() {
        //残り時間が1秒以上あるか
        if currentTime >= 1 {
            currentTime -= 1
            currentTimeLabel.text = "\(currentTime)"
        } else {
            timer.invalidate()
            start.isHidden = false
            currentTime = 15
            currentTimeLabel.text = "\(currentTime)"
        }
    }

}

バックグラウンドの設定

SceneDelegateでは、
・バックグラウンドでの経過時間を取得
・デリゲートによるバックグラウンド関連の処理
を任せています。

バックグラウンドでの経過時間を取得

バックグラウンド中の時間は、
アプリ画面へ復帰した時の時刻 - バックグラウンドへ移行時の時刻
で算出してます。

UserDefaultで移行時の時刻を一時保持しておき、アプリ画面へ復帰する際に現在時刻を取得し、2つの時刻を引けば、指定した単位で時間を算出(今回は秒)。

SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    let ud = UserDefaults.standard

    //アプリ画面に復帰した時
    func sceneDidBecomeActive(_ scene: UIScene) {
        if バックグラウンドに移行した {
            let calender = Calendar(identifier: .gregorian)
            let date1 = ud.value(forKey: "date1") as! Date
            let date2 = Date()
            let elapsedTime = calender.dateComponents([.second], from: date1, to: date2).second!
            /*ここで経過時間をタイマーに渡す*/
        }
    }

    //アプリ画面から離れる時(ホームボタン押下、スリープ)
    func sceneWillResignActive(_ scene: UIScene) {
        ud.set(Date(), forKey: "date1")
        /*ここでバックグラウンドへの移行を検知*/
        /*ここでタイマーを破棄*/
    }
}

デリゲートの設定

デリゲートで実装したいのは、
バックグラウンドへの移行を検知
バックグラウンド時にタイマーを破棄
バックグラウンドの経過時間をタイマーに渡す
の3つです。

そして、用意するデリゲートの変数、関数は、下記の通り。

SceneDelegate.swift
protocol backgroundTimerDelegate: class {
    //バックグラウンドの経過時間を渡す
    func setCurrentTimer(_ elapsedTime:Int)
    //バックグラウンド時にタイマーを破棄
    func deleteTimer()
    //バックグラウンドへの移行を検知
    func checkBackground()
    //バックグラウンド中かどうかを示す
    var timerIsBackground:Bool { set get }
}

SceneDelegate.swift

デリゲートを検知する側(SceneDelegate.swift)にデリゲートを作っておき、デリゲートメソッドで復帰後にタイマーを再実行できるように諸々を処理しています。

SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    //デリゲート
    weak var delegate: backgroundTimerDelegate?
    let ud = UserDefaults.standard

    //アプリ画面に復帰した時
    func sceneDidBecomeActive(_ scene: UIScene) {
        //タイマー起動中にバックグラウンドへ移行した?
        if delegate?.timerIsBackground == true {
            let calender = Calendar(identifier: .gregorian)
            let date1 = ud.value(forKey: "date1") as! Date
            let date2 = Date()
            let elapsedTime = calender.dateComponents([.second], from: date1, to: date2).second!
            //経過時間(elapsedTime)をbackgroundTimer.swiftに渡す
            delegate?.setCurrentTimer(elapsedTime)
        }
    }

    //アプリ画面から離れる時(ホームボタン押下、スリープ)
    func sceneWillResignActive(_ scene: UIScene) {
        ud.set(Date(), forKey: "date1")
        //タイマー起動中からのバックグラウンドへの移行を検知
        delegate?.checkBackground()
        //タイマーを破棄
        delegate?.deleteTimer()
    }
}

backgroundTimer.swift

デリゲートの処理を実行する側(backgroundTimer.swift)にデリゲート、デリゲートの変数・関数郡の設定をします。

backgroundTimer.swift
class backgroundTimer: UIViewController,backgroundTimerDelegate {

    @IBOutlet weak var currentTimeLabel: UILabel!
    @IBOutlet weak var start: UIButton!
    //タイマー起動中にバックグラウンドに移行したか
    var timerIsBackground = false
    var timer:Timer!
    var currentTime = 15

    override func viewDidLoad() {
        super.viewDidLoad()
        //SceneDelegateを取得
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
            let sceneDelegate = windowScene.delegate as? SceneDelegate else {
            return
        }
        //デリゲートを設定
        sceneDelegate.delegate = self
    }

    func checkBackground() {
        //バックグラウンドへの移行を確認
        if let _ = timer {
            timerIsBackground = true
        }
    }

    func setCurrentTimer(_ elapsedTime:Int) {
        //残り時間から引数(バックグラウンドでの経過時間)を引く
        currentTime -= elapsedTime
        currentTimeLabel.text = "\(currentTime)"
        //再びタイマーを起動
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(advancedTime), userInfo: nil, repeats: true)
    }

    func deleteTimer() {
        //起動中のタイマーを破棄
        if let _ = timer {
            timer.invalidate()
        }
    }

}
バックグラウンドへの移行を検知

タイマー起動中にバックグラウンドへの移行した時、のみを検知するために、タイマーが起動中かどうかをif文でチェックしています。

もしチェックしなければ、タイマーを起動してない状態からバックグラウンドへの移行も検知してしまいます。

backgroundTimer.swift
    func checkBackground() {
        //バックグラウンドへの移行を確認
        if let _ = timer {
            timerIsBackground = true
        }
    }
バックグラウンドへの移行時にタイマーを破棄

checkBackground()と同様に、タイマーが起動中かをチェックしてから、タイマーを破棄しています。

残念ながら、Timerクラスには一時停止する機能がないので、タイマー処理を止めるには破棄するしかありません。

backgroundTimer.swift
    func deleteTimer() {
        //起動中のタイマーを破棄
        if let _ = timer {
            timer.invalidate()
        }
    }
バックグラウンドでの経過時間をタイマーに渡す

SceneDelegateで算出した経過時間を引数でタイマーに渡します。

バックグラウンドへ移行時にタイマーが破棄されたので、残り時間のcurrentTimeはカウントダウンされないままだったので、引数で受け取った経過時間を引きます。

また、アプリ画面へ復帰時に実行されるので、タイマー処理を再実行してます。

backgroundTimer.swift
    func setCurrentTimer(_ elapsedTime:Int) {
        //残り時間から引数(バックグラウンドでの経過時間)を引く
        currentTime -= elapsedTime
        currentTimeLabel.text = "\(currentTime)"
        //再びタイマーを起動
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(advancedTime), userInfo: nil, repeats: true)
    }

実装結果

ホームボタン押下、スリープ、の順にバックグラウンドへ移行してます。

output.gif

ソースコード

backgroundTimer.swift
import UIKit

class backgroundTimer: UIViewController,backgroundTimerDelegate {

    @IBOutlet weak var currentTimeLabel: UILabel!
    @IBOutlet weak var start: UIButton!
    //タイマー起動中にバックグラウンドに移行したか
    var timerIsBackground = false
    var timer:Timer!
    var currentTime = 15

    override func viewDidLoad() {
        super.viewDidLoad()
        //SceneDelegateを取得
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
            let sceneDelegate = windowScene.delegate as? SceneDelegate else {
            return
        }
        sceneDelegate.delegate = self
        currentTimeLabel.text = "15"
    }

    @IBAction func start(_ sender: Any) {
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(advancedTime), userInfo: nil, repeats: true)
        start.isHidden = true
    }

    @objc func advancedTime() {
        if currentTime >= 1 {
            currentTime -= 1
            currentTimeLabel.text = "\(currentTime)"
        } else {
            timer.invalidate()
            start.isHidden = false
            currentTime = 15
            currentTimeLabel.text = "\(currentTime)"
        }
    }

    func checkBackground() {
        //バックグラウンドへの移行を確認
        if let _ = timer {
            timerIsBackground = true
        }
    }

    func setCurrentTimer(_ elapsedTime:Int) {
        //残り時間から引数(バックグラウンドでの経過時間)を引く
        currentTime -= elapsedTime
        currentTimeLabel.text = "\(currentTime)"
        //再びタイマーを起動
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(advancedTime), userInfo: nil, repeats: true)
    }

    func deleteTimer() {
        //起動中のタイマーを破棄
        if let _ = timer {
            timer.invalidate()
        }
    }

}
SceneDelegate.swift
import UIKit

//デリゲート用の変数、関数
protocol backgroundTimerDelegate: class {
    func setCurrentTimer(_ elapsedTime:Int)
    func deleteTimer()
    func checkBackground()
    var timerIsBackground:Bool { set get }
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    weak var delegate: backgroundTimerDelegate?
    let ud = UserDefaults.standard

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    //アプリ画面に復帰した時
    func sceneDidBecomeActive(_ scene: UIScene) {
        if delegate?.timerIsBackground == trued {
            let calender = Calendar(identifier: .gregorian)
            let date1 = ud.value(forKey: "date1") as! Date
            let date2 = Date()
            let elapsedTime = calender.dateComponents([.second], from: date1, to: date2).second!
            delegate?.setCurrentTimer(elapsedTime)
        }
    }

    //アプリ画面から離れる時(ホームボタン押下、スリープ)
    func sceneWillResignActive(_ scene: UIScene) {
        ud.set(Date(), forKey: "date1")
        delegate?.checkBackground()
        delegate?.deleteTimer()
    }
}

参考

[iOS]バックグラウンドで長時間BLE通信続ける方法
【Swift 4.2】 アラーム時計の作り方 - Qiita
[Swift] iOSのバックグラウンド処理について - Qiita
iOSにおけるバックグラウンド処理の全体感 - Qiita
[iOS][小ネタ] アプリのバックグラウンド実行を禁止する方法 | Developers.IO

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

Swiftで画像を丸くする方法(かんたん!)

imageViewのレイヤーのcornerRadiusというプロパティにviewの横幅の半分の値をセットすると、きれいな円になってくれます。

sampleImage.layer.cornerRadius = sampleImage.frame.width / 2.0

IMG_4346.jpg

参考文献

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

Swift ?? 演算子について

Swift ??演算子について

??とは、値が存在しない場合のデフォルト値を指定する演算子

こんな感じに使うらしい

let a: Int = 5
var b: Int? 

let c: Int = b ?? a
print("\(c)") //5

具体的にどういうことかというと、

let a: Int = 5
var b: Int?

let d: Int = b != nil ? b! : a

言語化して伝えると

①定数a = 5と定義
②nilかIntが入っているbを定義
③dは、bがnilじゃなかったら、bをセット。ただし、bにnilが入っていたらエラーを出す。
bがnilだったら、aをセット

もう一度見てみましょう、

let c: Int = b ?? a

bがnilじゃなかったらbをセット
nilだったら、aをセット

皆さんの理解が深まれば、幸いです。?

参照URL
http://www.kuma-de.com/blog/2016-04-12/7172

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

UICollectionViewで画像を横スクロールする方法

Swift初学者の筆者がCollectionViewの使い方の備忘録として執筆しました。

超参考になるCollectionViewの使い方を参考に本記事を執筆しましたので、参考サイトにLGTMお願いします。

完成品

Demo-horizontalCell.gif
CollectionViewを使い、画面を横スクロールすることで画像(表示内容)を動かすアプリケーションです。

storybordの設定

Main.storybordで行う設定

mainstorybord.png

CollectionViewの設定

  • AutoLayoutをする
  • ViewController.swiftとIBoutlet接続する
  • ViewController.swiftと紐付けて「dataSource」と「delegate」を編集できるよにする。
    (CollectionViewにマウスを合わせて、"control"を押しながらViewControllerの黄色い丸印までドラッグ・アンド・ドロップ)
  • 属性インスペクタの[Scroll Direction]を[Horizontal]に設定
    ([Scroll Direction]でスワイプの方向を指定することができる)
    scrolldirection2horizontal.png

CollectionViewCellの設定

  • xibと紐づいているファイルをclassに指定する。
  • [identifler]を「cell」にします。

xibで行う設定

ImageViewを追加してください。

ImageViewの設定

  • AutoLayoutをする
  • CollectionViewCell.swiftとIBoutlet接続する

コード

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet var horizontalCollectionView: UICollectionView!

    var viewWidth: CGFloat!
    var viewHeight: CGFloat!
    var cellWitdh: CGFloat!
    var cellHeight: CGFloat!
    var cellOffset: CGFloat!
    var navHeight: CGFloat!

    var photoArray = ["baseball","soccer"]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        viewWidth = view.frame.width
        viewHeight = view.frame.height
        //ナビゲーションバーの高さ
        navHeight = self.navigationController?.navigationBar.frame.size.height

        //xib読み込み
        let nib = UINib(nibName: "CollectionViewCell", bundle: .main)
        horizontalCollectionView.register(nib, forCellWithReuseIdentifier: "cell")

        //空の背景画像を設定
        self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
        //ナビゲーションバーの影画像を空に設定
        self.navigationController!.navigationBar.shadowImage = UIImage()
        //アイテムの色を指定
        self.navigationController?.navigationBar.tintColor = .white
        //タイトル色の指定
        self.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
    }
}

//ViewControllerのプロトコル定義
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    //collectionViewの要素の数を返す
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photoArray.count
    }

    //collectionViewのセルを返す(セルの内容を決める)
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
        cell.backgroundImageView.image = UIImage(named: photoArray[indexPath.row])
        return cell
    }

    //セル間の間隔を指定
    private func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimunLineSpacingForSectionAt section: Int) -> CGFloat {
        return 24
    }

    //セルのサイズ(CGSize)
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        cellWitdh = viewWidth-75
        cellHeight = viewHeight-300
        cellOffset = viewWidth-cellWitdh
        return CGSize(width: cellWitdh, height: cellHeight)
    }

    //余白の調整(UIImageを拡大、縮小している)
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        //top:ナビゲーションバーの高さ分上に移動
        return UIEdgeInsets(top: -navHeight,left: cellOffset/2,bottom: 0,right: cellOffset/2)
    }

}
CollectionViewCell.swift
import UIKit

class CollectionViewCell: UICollectionViewCell {

    @IBOutlet var backgroundImageView: UIImageView!
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

}

(プログラムは、https://qiita.com/misakiagata/items/acf869a5c7fc4cebaaad から引用しました。)

参考サイト

https://qiita.com/misakiagata/items/acf869a5c7fc4cebaaad

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

GitHub奮闘記②【手順まとめ】

 ㊗︎ 初 push

用語に続いて、この勢いで 手順もまとめます。
GitHubのユーザー登録は済ませて下さい。

 手順

 0.最終的に、こんな感じ。

「GitTestProject」というリポジトリ名。
initial Commitに加えて、追加したCommitが反映されています。

push出来てた!.png

 1. gitの初期設定?

Gitに「ユーザ名」「メールアドレス」を登録します。
GitHubと同じものを指定しておくのが良いです。

以下のコードを元に、ターミナルで設定します。(1行ずつ)

git config --global user.name "ユーザー名"
git config --global user.email メールアドレス

ターミナル ?

Xcode内にターミナルは無いですが、Macに標準インストールされてます。
Finder -> アプリケーション -> ユーティリティ -> ターミナル

 2. プロジェクト開始と同時に、リポジトリを作成?

Create Git repository on (My Mac)にチェック。✅
initial Commitされます。

20160301231957.png

Commit = 変更履歴を、ローカルリポジトリに保存すること。

✅により、プロジェクト開始と同時にローカルリポジトリが作成され、
そこにソースコードが保存されました。

inital Commit.png

initial Commitでは、デフォルトのコードしかCommitされません。

viewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    } 
}

 補足

Xcodeメニューバーにて、Source Control > Create Git Repositories...からでもinitial Commit出来ますが、

リポジトリを「✅」で作成するデメリットは特に無いので、
チェック入れるのが吉。

 3. リモートリポジトリを作成する。?

リモートリポジトリ作成(pushing ぐるぐる).png

image.png

Remoteを右クリック > Create "GitTestProject" Remote..
で、Create画面が出ます。

createを押すと、「Pushing...」 ぐるぐるLoadingされます。?

ご自身でGitHubにログインして、
リモートレポジトリが作られているか確認してみましょう。

initial Commit のみがpushされていると思います。
無事、push完了です?

 4. コード変更後、Xcodeでプッシュする。?

さて、『いつ、誰が、どこに、どのような変更を行ったか』。
バージョン管理がGitの利点です。

実際にコードを変更して、Commitして、pushしてみます。

 コードいじる。

ViewController.swiftを修正後、Commitします。
今回は以下のコードを追記してみました。

ViewController.swift
 func Log() {
        print("Hello")
    }

 変更したので、Commitする。

ゲームでいう、「セーブ」

  • Mのついたファイルを右クリックして、Source Control > Commit "ViewController.swift"... (M= Modify: 修正する)
  • Xcodeメニューバーからでも、出来ます。

こんな感じ。
変更箇所が、とても分かりやすいです。
ローカルリポジトリにCommit.png

 Commit完了!

Commit 完成.png

 補足

Commit Message欄には、『どのような変更を行ったか』を記載します。
書かないと、Commitを実行できません。
Commit Messages is required.png

 コード変更したので、Xcodeでプッシュする。

Xcodeメニューバーにて、Source Control > Push...

push出来てた!.png

GitHubを確認。
無事、pushできています✌️

変更履歴がちゃんと表示されてます!.png

変更箇所も分かりやすい。
これで共同開発チームの皆が、コード変更を見ることができます。?

おしまい。

 参考サイト

Xcodeでgit機能を使う(プロジェクトの作成からgithubにpushするまで)
XcodeからgitとGitHubを使う方法・基本編

 関連記事

追記予定

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

GitHub奮闘記①【用語まとめ】

 ㊗︎ 初 push

先日まで push が出来なかったのですが、

gitの初期設定をしていなかった事が原因でした。
以下のコードを元に、ターミナルで設定します。(1行ずつ)

git config --global user.name "ユーザー名"
git config --global user.email メールアドレス

ターミナル ?

Xcode内にターミナルは無いですが、Macに標準インストールされてます。
Finder -> アプリケーション -> ユーティリティ -> ターミナル

 用語

 「リポジトリ」 (repository)

- ソースコードを管理する単位。
-「保管場所」をカッコ付けて言った表現。
-「ローカル」と「リモート」 がある。
- ローカルリポジトリで作業を行い、その作業内容をリモートリポジトリへpush。
- 
今さら聞けない!GitHubの使い方【超初心者向け】

 「ソースコード」

人間が理解しやすいプログラミング言語を使って、記述されたもの。

プログラムの"元" (source)なので、ソースプログラムとも言われる。

ソースコードをコンパイルして、プログラムが作られる。
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

 「バージョン管理」

システムの変更を管理すること。
『いつ、誰が、どこに、どのような変更を行ったか』

Webデザイン, Webライターとかにも使われる。

 ちょっと、ややこしい用語?

Git と GitHub

-Git ソースコードのバージョンを管理するためのシステム。

-GitHub GitHub社が作ったサービス。Gitに、便利な機能を付け加えた。

 「Git」

download-1.png!

リーナス・トーバルズ氏が開発。
Linux?も開発した、すごい人らしい。


 「GitHub」

download.png

よく見かける生物。
以下、GitHub共同創業者さんのインタビュー引用。

コマンドラインで使うにはGitは最高だったんですが、
オンラインでコードをシェアするのが難しかったのです。

他の開発者と、コードをシェアしたかったのです。
GitHubは、週末のサイドプロジェクトとしてスタートしました。

共同創業者に聞いた、GitHubは何が違ったのか?

push と commit

-commit 変更履歴を、ローカルリポジトリに保存すること。
-push ローカルリポジトリ内の変更履歴を、GitHubに送ること。
(=リモートリポジトリ にアップロードすること。)

git_repository_figure.png

commit → ゲームのセーブ
push → セーブデータをサーバに保存
commit → メールの下書き保存
push → メールの送信

おしまい。

 関連記事

追記予定

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

[Swift] UIBezierPathで簡単なアイコンサンプルを作成する

はじめに

アプリ開発をしていて簡易的にアイコンを設定したいと思ったときに、
単純なものならUIBezierPathを使用してサックっと作成してしまうのも良いかもと思いサンプルを作成しました。

対象バージョン

  • Xcode ver.11.6
  • iOS ver 13.5.1

完成サンプル

image.png

UIBezierPathView.swift
import UIKit

class UIBezierPathView: UIView {
    private var type: Int = 0

    init(frame: CGRect, type: Int) {
        super.init(frame: frame)
        self.type = type
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        backgroundColor = UIColor(red: 88/255, green: 149/255, blue: 223/255, alpha: 1)
        super.layoutSubviews()
        layer.cornerRadius = frame.width / 2
        layer.masksToBounds = true
    }

    override func draw(_ rect: CGRect) {
        switch type {
        case 1:
            // プラス
            let verticalPath = UIBezierPath();
            verticalPath.move(to: CGPoint(x: rect.midX, y: 15));
            verticalPath.addLine(to: CGPoint(x: rect.midX, y: rect.maxY - 15));
            verticalPath.close()
            UIColor.white.setStroke()
            verticalPath.lineWidth = 4
            verticalPath.stroke();

            let horizontalPath = UIBezierPath();
            horizontalPath.move(to: CGPoint(x: 15, y: rect.midY));
            horizontalPath.addLine(to: CGPoint(x: rect.maxX - 15, y: rect.midY));
            horizontalPath.close()
            UIColor.white.setStroke()
            horizontalPath.lineWidth = 4
            horizontalPath.stroke();
        case 2:
            // バツ
            let rightPath = UIBezierPath();
            rightPath.move(to: CGPoint(x: 18, y: 18));
            rightPath.addLine(to: CGPoint(x: rect.maxX - 18, y: rect.maxY - 18));
            rightPath.close()
            UIColor.white.setStroke()
            rightPath.lineWidth = 4
            rightPath.stroke();

            let leftPath = UIBezierPath();
            leftPath.move(to: CGPoint(x: rect.maxX - 18, y: 18));
            leftPath.addLine(to: CGPoint(x: 18, y: rect.maxY - 18));
            leftPath.close()
            UIColor.white.setStroke()
            leftPath.lineWidth = 4
            leftPath.stroke();
        case 3:
            // 四角
            let squarePath = UIBezierPath(rect: CGRect(x: 15, y: 15, width: 18, height: 18))
            UIColor.white.setStroke()
            squarePath.lineWidth = 4.0
            squarePath.stroke()
        case 4:
            // 三角
            let trianglePath = UIBezierPath();
            trianglePath.move(to: CGPoint(x: rect.maxX - 15, y: rect.midY));
            trianglePath.addLine(to: CGPoint(x: 20, y: 15));
            trianglePath.addLine(to: CGPoint(x: 20, y: rect.maxY - 15));
            trianglePath.close()
            UIColor.white.setStroke()
            trianglePath.lineWidth = 4.0
            trianglePath.stroke();
        case 5:
            // 三本線
            let lineOnePath = UIBezierPath();
            lineOnePath.move(to: CGPoint(x: 15, y: 18));
            lineOnePath.addLine(to: CGPoint(x: rect.maxX - 15, y: 18));
            lineOnePath.close()
            UIColor.white.setStroke()
            lineOnePath.lineWidth = 4
            lineOnePath.stroke();

            let lineTwoPath = UIBezierPath();
            lineTwoPath.move(to: CGPoint(x: 15, y: 25));
            lineTwoPath.addLine(to: CGPoint(x: rect.maxX - 15, y: 25));
            lineTwoPath.close()
            UIColor.white.setStroke()
            lineTwoPath.lineWidth = 4
            lineTwoPath.stroke();

            let lineThreePath = UIBezierPath();
            lineThreePath.move(to: CGPoint(x: 15, y: rect.maxY - 18));
            lineThreePath.addLine(to: CGPoint(x: rect.maxX - 15, y: rect.maxY - 18));
            lineThreePath.close()
            UIColor.white.setStroke()
            lineThreePath.lineWidth = 4
            lineThreePath.stroke();
        default:
            // 円
            let circlePath = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.midY), radius: 10, startAngle: 0, endAngle: CGFloat(Double.pi) * 2, clockwise: true)
            UIColor.white.setStroke()
            circlePath.lineWidth = 4.0
            circlePath.stroke()
        }
    }
}
UIBezierPathViewController.swift
import UIKit

class UIBezierPathViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white

        let circleView = UIBezierPathView(frame: CGRect(x: 0, y: 50, width: 50, height: 50), type: 0)
        circleView.center.x = view.center.x
        view.addSubview(circleView)

        let plusView = UIBezierPathView(frame: CGRect(x: 0, y: 150, width: 50, height: 50), type: 1)
        plusView.center.x = view.center.x
        view.addSubview(plusView)

        let closeView = UIBezierPathView(frame: CGRect(x: 0, y: 250, width: 50, height: 50), type: 2)
        closeView.center.x = view.center.x
        view.addSubview(closeView)

        let squareView = UIBezierPathView(frame: CGRect(x: 0, y: 350, width: 50, height: 50), type: 3)
        squareView.center.x = view.center.x
        view.addSubview(squareView)

        let triangleView = UIBezierPathView(frame: CGRect(x: 0, y: 450, width: 50, height: 50), type: 4)
        triangleView.center.x = view.center.x
        view.addSubview(triangleView)

        let threeLineView = UIBezierPathView(frame: CGRect(x: 0, y: 550, width: 50, height: 50), type: 5)
        threeLineView.center.x = view.center.x
        view.addSubview(threeLineView)
    }
}

まとめ

シンプルなものなら簡単に描画できるので、忘れたときに参考にしてもらえればと思います。

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

[Swift勉強会] カウントアップアプリを改造して,通知をn秒後に通知を出してみる (n>0)

前回までの内容

カウントアップアプリの作成まではこちら
作:https://qiita.com/appgrape


今回のゴール

  • 通知の基本文法がわかる
  • UISwitchの基本的な使い方がわかる
  • Switchをオンにした時に通知される
    • アプリ内で通知が来る
  • カウントアップで指定した数秒後に通知が出せる

完成アプリ

完成品がこちらになります
ダウンロード (1).gif


アプリの実装


UISwitchの実装

UIの実装

前回までと同様にMain.StoryboardにUIパーツを配置する(今回はUISwitch)
スクリーンショット 2020-08-07 17.18.53.png

次に設置したUISwitchをクリックし,画面右のAttributes Inspectorをクリックする
するとAttributes Inspectorの一番上に,Switchの初期状態をセットするStateがあるのでオンからオフに変える
スクリーンショット 2020-08-07 17.26.54.png

そしてAssistant Editorを起動してViewController.SwiftにUISwitchを紐付ける.
スクリーンショット 2020-08-07 17.25.05.png

名前はonSwitchDidChangedとした.
画像のようにTypeUISwitchとする

コードの実装

次にコードを書く

ViewController.swift
@IBAction func onSwitchDidChanged(_ sender: UISwitch) {
    //switchを押すとこの中身が動く
    if sender.isOn {
        //switchをオンにするとswitch tapped!が出力される
        print("switch tapped!")
    }
}

実際に動かしてみる
2020-08-08 at 21.28.38.png

image.png


通知の実装

通知の実装ではStoryboardをいじることはない
(全部コードで完結する)


通知の許可を取る

通知機能のあるiOSアプリには,まず起動時に通知の許可を取る必要がある
次のコードを追加する

AppDelegate.swift
import UIKit
import UserNotifications //<-通知関係を使用する時に必要
//~~(中略)~~
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
//ここから
        // 通知許可の取得
        UNUserNotificationCenter.current().requestAuthorization(
        options: [.alert, .sound, .badge]){ //許可を取るもの:
                                            // .alert -> 通知のポップアップを許可するか,
                                            // .sound -> 通知音を許可するか,
                                            // .badge -> 通知時にアプリアイコンに通知数の表示を許可するか
            (granted, _) in
            if granted{
                UNUserNotificationCenter.current().delegate = self
            }
        }
//ここまで追加
    return true
}
//~~(中略)~~
//コード末尾に追加
extension AppDelegate: UNUserNotificationCenterDelegate{
}

2020-08-08 at 21.28.38.png

このコードを書くと,アプリ起動時に1回だけ通知許可のポップアップが出てくる.
ただこれは許可を取っただけなので,通知自体は実装されていない.


通知のテストをしてみる

前のページで通知の許可を取ったため,次は実際にswitchを押して通知させてみる.
次のコードを追加する

ViewController.swift
import UIKit
import UserNotifications //<-通知関係を使用する時に必要
//~~(中略)~~
@IBAction func countDounButton(_ sender: Any) {
}
//ここから
@IBAction func onSwitchDidChanged(_ sender: UISwitch) {
    //switchを押すとこの中身が動く
    if sender.isOn {
        //switchをオンにすると通知が来る
        showNotification()
    }
}

func showNotification() {
    let content = UNMutableNotificationContent() //<-通知のコンテンツを入れる定数contentを初期化
    content.title = "countUpAppForBeginners" //<-通知のタイトル
    content.body = "switchをオンにしました。" //<-通知の詳細文
    content.sound = UNNotificationSound.default //<-通知の音はデフォルトのやつ

    let request = UNNotificationRequest(identifier: "changedSwitch", content: content, trigger: nil)
    //通知のリクエストを任意の名前(identifier),内容(content),発動条件(trigger)をつけて定数に保存する

    UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    //通知センターに通知のリクエストを追加する
}

実際に試してみる
ダウンロード (2).gif

動かない


実はlet requestのtriggerがnilだと,即座に通知が呼ばれる
しかし通知するアプリが起動中は通知はこない(設定でアプリ内でも通知可能)
つまり通知は機能してるが見えない状態
なので3秒後に通知に設定して確認する


3秒後に通知してみる

ViewController.swiftに次のコードを追加,編集する

ViewController.swift
func showNotification() {
    let content = UNMutableNotificationContent()
    //content:通知に表示するものを編集する
    content.title = "countUpAppForBeginners"
    content.body = "switchをオンにしました。"
    content.sound = UNNotificationSound.default

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(3), repeats: false) //3秒後に通知させる

    let request = UNNotificationRequest(identifier: "changedSwitch", content: content, trigger: trigger)//<-ここをnilからtriggerに変える
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    }

試してみる
ダウンロード (1).gif

注意点としてtrriger=0だとエラーが出る


n秒後に通知してみる(n > 0)

最後にカウントアップで設定した値秒後に通知を実装する
前回のところでtrriger=0だとエラーが出ると述べた.
試していないがおそらく負の値でもエラーが出るのでカウントアップを1未満にならないように設定する.

ViewControlelr.swift
//~~(前略)~~
//数字を格納する場所
var count = 1 //<- 初期値を0から1に
//~~(中略)~~
@IBAction func countDounButton(_ sender: Any) {
    //-ボタンを押すとラベルの文字をカウントダウン
    if count > 1 {
        //countが1未満にならないようにカウントダウンさせる
        count = count - 1
        countLabel.text = String(count)
    }
    //カウントにあわせて文字の色を変更
    changeTextColor()
}
//~~(中略)~~
//n秒後に通知させる(n>0)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(count), repeats: false) //countをTimerIntervalに変換する
//~~(後略)~~

試してみる
ダウンロード.gif

これで完成


おまけ:アプリ内で通知させてみる

AppDelegateに次のコードを追加するだけ

AppDelegate.swift
extension AppDelegate: UNUserNotificationCenterDelegate{
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // アプリ起動中でも通知させる
        completionHandler([.alert, .sound])
    }
}

最後に

今回はUISwitchの使い方と通知の実装をざっくりと説明しました.
質問,訂正があればコメント欄かtwitterにお願いします

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

[swift]エラーThread 1: Exception: "App ID not found. Add a string value with your app ID for the key FacebookAppID to the Info.plist or call [FBSDKSettings setAppID:]."の対処法

エラー内容

Firebaseを用いてFacebookログインを実装しており、シュミレーター(実機)でログインボタンをクリックし動作確認を行った際に、画面遷移されずアプリケーションがフリーズしてしまうエラーです。

Xcodeを確認したところThread 1: Exception: "App ID not found. Add a string value with your app ID for the key FacebookAppID to the Info.plist or call [FBSDKSettings setAppID:].というエラーが発生しておりました。

環境▼
Swift version 5.2.4
Xcode version 11.6

仮説

エラー文を翻訳すると"アプリのIDが見つからないので、info.plistにアプリIDを登録するか、[FBSDKSettings setAppID:]を用いて文字列の値を追加するかをして下さい。"

というようにまとめることができます。

エラー文を元に仮説を立てると...

仮説①info.plistのアプリIDの登録を誤っている。
仮説②ファイル内のいずれかに"FBSDKSettings setAppID:"を用いてString型でアプリIDを宣言する。

といったように候補が挙がる。一個ずつ確認するしかないですね!

仮説を元に対応したこと

まず仮説①を検証します。
image.png
info.plist/Property Listを確認したところ問題なさそう。
念の為info.plist/Open As/Source Codeも確認。(下記画像)
image.png
ん?<key>FacebookAppID</key>の箇所の頭に空白がありますね。
もしかしてこれが原因かな?

仮設検証

訂正して再ビルド、シミュレーターにて動作確認...
無事、画面遷移されFacebookログインが実行されました!

今回はエラー文を翻訳すると、ヒントが記されていましたね。
ちなみに私は翻訳機能としてDeepL翻訳というアプリを使っています!
https://www.deepl.com/ja/translator

よかったら参考にしてみて下さい。

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

アプリ道場サロンの課題チャレンジPart1

iOSアプリ開発をテーマとしたオンラインサロンに入っています。
https://community.camp-fire.jp/projects/view/281055

その中で課題があって、やってみたい人は自由に課題にチャレンジする事ができます。
やった課題は、サロン主からフィードバックがもらえます。

課題

最初の課題は数字を足し算するアプリです。
https://www.youtube.com/watch?v=ICsj-14nhBc&list=PLQ5rERkGSxF-fsdBNQu70r5r0OPwSVygO&index=2&t=0s

この課題で調べものをしたのは下記です。

・キーボードの閉じ方
https://qiita.com/jumpyoshim/items/4b8b5f2297910d7f3d1b

・ストーリーボードで並んでいるViewを配列として扱うやり方(StackView)
https://qiita.com/yucovin/items/ff58fcbd60ca81de77cb

結果

下記のようなアプリを作れました。
screenshot.png

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