20200914のSwiftに関する記事は7件です。

配列について

1.はじめに


今まで範囲型やストライド型などを解説してきましたが、最も基本的ともいえるであろう配列について何も触れていなかったので、今回は解説していこうと思います。

2.配列とは


説明は不要だとは思いますが、念のために説明しておきます。
配列とはArray型のことを指していて、Array型とは1つの箱の中に、いくつかのデータが格納されているもののことを言います。このように複数個の要素を格納できるデータ構造のことを、コレクションといいます。

3.コレクションの内容の変更


コレクションの内容を変更するには、それが定数ではなく、変数に代入されている必要があります。
今説明しているArray型は構造体として定義されているデータ型で、配列のインスタンスを変数に代入したり、関数に対して引数として渡したりする際、必要に応じて新しいインスタンスが作成され、元の配列の内容に影響しないように動作します。
よってコレクションを変更したい場合、元の配列の値を保持するのか、または変更してもいいのかについて、検討する必要があります。
配列に対する操作には、配列の内容を変更するものと、新しい配列を値として返すものの2パターン用意されている場合があります。
例えば内容を昇順にソートするメソッドには、配列の内容を変更するsort()と、ソートされた配列を返すsorted()があります。

tmp1.sort()                //変数tmp1の内容が変更される
let tmp3 = tmp2.sorted()   //定数tmp2の内容は変更されず、結果はtmp3に入る

4.配列の部分的な置換


配列の添字として範囲を指定しておき、別の配列を代入すると、その部分を新しい内容に置き換えることができます。置き換える前と後の要素の個数は一致していなくても大丈夫です。以下の例を確認してください。

var s = ["春", "夏", "秋", "冬"]

s[0...0] = ["春休み", "お花見", "入学式"]  // s[0]はエラー
print(s)        // ["春休み", "お花見", "入学式", "夏", "秋", "冬"]を出力

s[1...3] = ["梅雨", "夏休み", "プール"]
print(s)        // ["春休み", "梅雨", "夏休み", "プール", "秋", "冬"]を出力

s[3...4] = []   // 削除にも使える
print(s)        // ["春休み", "梅雨", "夏休み", "冬"]を出力

5.部分配列の型


先ほど例を示したように、配列の添字に範囲を指定すると部分配列を得ることができます。
片側範囲を指定することもでき、先頭から途中まで、または途中から末尾までの部分配列になります。[...]ですべての要素を含む部分列も指定できます。以下を確認してください。

var days = ["日", "月", "火", "水", "木", "金", "土"]
print(days[2...4])  // ["火", "水", "木"]
print(days[...2])   // ["日", "月", "火"]
print(days[5...])   // ["金", "土"]
print(days[...])    // すべての要素を表示

ただし、得られた部分配列の型はArray型ではなく、ArraySlice型という型になります。
ArraySlice型も同様に利用できますが、添字を使って要素にアクセスする場合には、部分配列を指定したときの範囲で利用可能になるので、注意が必要です。
つまり必ずしも0から添字が使えるわけではなく、以下の例ならば3、4のみが利用できます。

let sub = days[3..<5]   // subはArrayIndex型。["水", "木"]
print(sub.count)        // 3を出力(要素が3つ)
print(sub.startIndex)   // 3を出力(最初の添え字は3)
print(sub[3])           // "水"を出力
print(sub[4])           // "木"を出力

ここでArray型が必要になったときに、ArraySlice型からArray型を得ることができます。

print(sub[3])                 // subはArraySlice型("水"を出力)
let subarray = [String](sub)  // Array<String>のイニシャライザ
print(subarray[0])            // 添字が0からになる("水"を出力)

6.配列同士の比較


配列同士を比較するために、「==」と「!=」を利用することができます。
「==」は、2つの配列の要素数が等しく、先頭から順番に対応する要素がそれぞれ等しいときにのみ真になります。以下の例を見てください。

let a = [1, 2]
let b = [2, 1]
a == b                 // false
a + [1] == [1] + b     // true([1, 2, 1]が構成されたため)

7.おわりに


応用的な部分にはあまり触れられていませんが、今回は様々な言語でとても中心的な要素となる、配列について解説しました。
利用する機会はすごく多いと思うので、基本的な活用方法などはきっちりと押さえておきましょう。

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

UIView の updateConstraints() について知る

今回は View の制約を更新する際などに登場する updateConstraints() について簡単にまとめていきます?

なんのメソッド?

このメソッドはUIView のインスタンスメソッドで、View の制約を更新する際に呼び出します。基本的にはシステム側がレイアウトパフォーマンスを考慮したタイミングで呼び出すので、開発者が意図的に呼び出してはいけません。

いつ使うのか?

ある View に指定する制約が遅くパフォーマンスに影響を与える場合や冗長な変更を多数している場合にのみ、UIView のサブクラスでメソッドを Override して使用します。また、更新処理の最後でsuper.updateConstraints() を呼び出す必要があります。

    override func updateConstraints() {
        //
        // 制約の更新
        //
        super.updateConstraints()
    }

基本的には、あるアクションに伴う制約の更新などをする際は逐次そのスコープで制約を更新した方がシンプルですが、先述の通りパフォーマンスを大きく損なうような制約の更新などの場合は updateConstraint() 内で行なった方がいいよ〜とのことです?これは、updateConstraint() がレイアウトののバッチ処理を行いメインスレッドをブロックしないためです。

意図的に呼び出したい場合は?

setNeedsUpdateConstraints() という UIView のインスタンスメソッドを使用して、制約更新のフラグをつけることができます。これによって、指定された View とその Subviews がシステムのタイミングによって updateConstraint() を呼ぶようになります。また、更新を即座に行う必要がある場合には updateConstraintsIfNeeded() を使うことで即座に制約更新を行わせることができます。

参考

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

iOS14 IDFA取得時の挙動を実機で検証してみる

2020/09/16:追記

iOS14 GMでも、同じ挙動となる事を確認しました。
日本時間 9月17日にリリースされる正式版でも恐らく同じ動きになる可能性が高いと思われます。

概要

iOS14(ベータ版)では、常にisAdvertisingTrackingEnabledがfalseを返してくる様です。

AdSupport FrameworkのisAdvertisingTrackingEnabled

iOS13までの「追跡型広告を制限」をチェックする為のフラグでしたが

iOS14でDeprecatedとなりました。

先日リリースされたiOS14 beta8で、実際の挙動について検証してみました。

環境

  • xcode12.0 beta 6
  • iOS14 beta8
  • iPhoneX(トラッキング制限設定無し)

結果

結果は以下の通り

ビルド isAdvertisingTrackingEnabled IDFA
iOS13向け false 取得OK
iOS14向け(IDFA許諾は未決定。ダイアログ出していない) false 取得OK

やはり、ユーザのIDFA許諾状況に依らず、isAdvertisingTrackingEnabledは常にfalseが返ってくる結果でした。

つまり、現在リリースしているアプリをiOS14で実行した場合。

iOS13で追跡型広告を制限(LAT)していなかったユーザであっても、iOS14では「制限あり」と判断してしまいます。
(従来コードのままだと、IDFAを取得しないフローに流れてしまう)

懸念

多くのアプリが、isAdvertisingTrackingEnabledをチェックせずにIDFAを取得するフローは、審査上取っていないと思います。

このままの仕様でリリースされた場合、

せっかくIDFA許諾必須化が来年初旬まで延期されたにも関わらず

この挙動に対応していないアプリからは「IDFAが取得出来ない」状況になる可能性が高いです。

対策

この仕様への対策として

AppTrackingTransparencyの許諾ステータスをチェック

許諾済み以外の場合、まずIDFAを取りにいき

返値がゼロ以外であれば、そのままIDFA使用

と言ったパターンなどが考えられます。

例えば

func isAvailableIDFA() -> Bool {
        if #available(iOS 14.0, *) {
            if ATTrackingManager.trackingAuthorizationStatus == .authorized {
                return true
            }else{
                // .authorized以外
                // 有効なIDFAか?
                return !isZeroIDFA()
            }
        }else{
            // iOS13以下
            return ASIdentifierManager.shared().isAdvertisingTrackingEnabled
        }
}

func isZeroIDFA() -> Bool {
        return (ASIdentifierManager.shared().advertisingIdentifier.uuidString == UUID(uuid: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)).uuidString)
}

ただ、この辺りの最終的な挙動はもうすぐ出るであろうGM版で、どうなるかまだわかりません。

引き続き、チェックしていきたいと思います。


参考
https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614148-isadvertisingtrackingenabled
https://developer.apple.com/documentation/apptrackingtransparency

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

無料のアプリアイコン作成ツール

はじめに

個人開発をする上で、アイコン作りは結構めんどくさい作業だと思っています。外注をしない場合は自分で作るしかありません。
そこで、個人的に使い心地がよかったツールを紹介します。ただし、この記事内では詳しい使い方に関しては説明していません。ご了承ください。

1. 元になる画像を作るツール

Pixlrが個人的にオススメです。ブラウザで動くのでダウンロードする必要もありませんし、無料でも使えます。
https://pixlr.com/jp/
背景を塗りつぶして、フリー素材のアイコンを貼り付ける程度の簡単な作業であれば問題なくできました。テキストの挿入もできるようです。

2. 作った画像からアイコンを作成するツール

https://makeappicon.com/
このツールは1枚の画像から、アイコンに必要な画像を作成してくれます。作成した画像はメールで送られてきます。

以上です。あとはアプリ側に送られてきた画像をアイコンとして登録してください。

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

Xcodeプロジェクト作成後にCoreDataを有効にする。

〜.xcdatamodeldファイルを生成する。

プロジェクト作成時にCoreDataを有効にしている場合は自動で作成されてます。後から追加する場合はこのファイルが必須になります。CoreDataはコマンドラインやxmlを使わずに、このフォームベースでデータベースの情報が登録できます。そのためには.xcdatamodeldが必須になります。

右クリックからNew Fileを選択して

スクリーンショット_2020-09-14_9_05_30.png

下にスクロールしてCore DataのData Modelを選択してください。

スクリーンショット_2020_09_14_9_08.png

保存したいデータの情報を登録する。

EntityとAttribute

Entityにはどんな情報を管理したいのかわかるような名前をつけます。タスク管理ならTask、メモ帳ならMemoなど、表の題名をイメージするとわかりやすいです。

Attributeには、Entityに保存する情報の単位とデータ型を決めます。タスクを新規追加した時間(Date)、タスク名(String)、タスクの有効/無効(Boolean)など、表の列名をイメージするとわかりやすい。

Model_xcdatamodel.png

NSManagedObjectを継承したクラスを作成する。

先程のCoreDataに作ったEntityとAttributeをSwiftからアクセスしてオブジェクトとして利用するためにNSManagedObjectを継承したデータクラスを作成します。

〜.xcdatamodeldを選択した状態でEditor->Create NSManagedObject Subclass...を選択して
案内通りに進みます。

スクリーンショット_2020-09-14_9_22_42.png

Task+CoreDataClass.swiftとTask+CoreDataProperties.swiftが生成されます。

ContentView_swift_と_「Xcodeプロジェクト作成後にCoreDataを有効にする。」を編集_-_Qiita.png

デフォルトの設定だと、NSManagedObjectを継承したクラスが存在しなくてもビルド時にxcodeが生成してくれますが、後々いろいろとやるために自分で作成して管理したほうが楽だと思いますので、この設定を無効にするためにcodegen(コードジェネレータ)をManual/Noneに変更します。

Model_xcdatamodel_と_「Xcodeプロジェクト作成後にCoreDataを有効にする。」を編集_-_Qiita.png

リスト形式でデータを表示するためにIdentifiableを継承させます。

Task+CoreDataProperties.swift
import Foundation
import CoreData


extension Task {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Task> {
        return NSFetchRequest<Task>(entityName: "Task")
    }

    @NSManaged public var id: UUID?
    @NSManaged public var name: String?
    @NSManaged public var isComplete: Bool
    @NSManaged public var dateAdded: Date?

}

//Identifiableに対応させるため追加
extension Task: Identifiable {
}

AppDelegate.swift

CoreDataライブラリを読み込ませ

import UIKit
import CoreData

AppDelegateクラスにpersistentContainerプロパティとsaveContextメソッドを追加

下のコードを追加します。


 lazy var persistentContainer: NSPersistentContainer = {

           let container = NSPersistentContainer(name: "Model")
           container.loadPersistentStores(completionHandler: { (storeDescription, error) in
               if let error = error as NSError? {                    
                   fatalError("Unresolved error \(error), \(error.userInfo)")
               }
           })
           return container
       }()

       func saveContext () {
           let context = persistentContainer.viewContext
           if context.hasChanges {
               do {
                   try context.save()
               } catch {
                   let nserror = error as NSError
                   fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
               }
           }
       }

NSPersistentContainerのnameに渡す文字列は.xcdatamodeldのファイル名になります。
自分が定義したファイル名に書き換えないとエラーになりますので注意してください。

Model.xcdatamodeldの場合

let container = NSPersistentContainer(name: "Model")

SceneDelegate.swift

CoreDataからデータを取得したり、新規データを追加するには、Contextが必要になります。ContentViewからContextを利用するためSceneDelegate.swiftを修正します。

let contentView = ContentView()

を下に変更

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let contentView = ContentView().environment(\.managedObjectContext, context)

ContentView.swift

import SwiftUI
import CoreData

struct ContentView: View {

    @Environment(\.managedObjectContext) var context

    @FetchRequest(
        entity: Task.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Task.dateAdded, ascending: false)],
        predicate: NSPredicate(format: "isComplete == %@", NSNumber(value: false))
    ) var notCompletedTasks: FetchedResults<Task>

    @State var newTaskName:String = ""

    func addTask(taskName: String, context: NSManagedObjectContext) {
        let task = Task(context: self.context)
        task.id = UUID()
        task.isComplete = false
        task.name = taskName
        task.dateAdded = Date()
        do {
            try self.context.save()
        } catch {
            print(error)
        }
    }

    func completeTask(_ task: Task, context: NSManagedObjectContext){
      let taskId = task.id! as UUID
        print(taskId)
      let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Task")
      fetchRequest.predicate = NSPredicate(format: "id == %@", taskId as CVarArg)
      fetchRequest.fetchLimit = 1
      do {
        let tasks = try context.fetch(fetchRequest)
        let targetTask = tasks[0] as! NSManagedObject
        targetTask.setValue(true, forKey: "isComplete")
        try context.save()

        print(targetTask)
      } catch {
        print(error)
      }
    }

    var body: some View {

        VStack {
            TextField("タスクを入力してください。", text: $newTaskName, onEditingChanged: { _ in
            }, onCommit: {
                self.addTask(taskName: self.newTaskName, context: self.context)
                self.newTaskName =  ""
            })
                .keyboardType(.default)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(32)

            List {
                ForEach(self.notCompletedTasks){ task in
                    Button(action: {
                        self.completeTask(task, context: self.context)
                    }) {
                        Text(task.name!)
                    }.onAppear() {
                        print(task.isComplete)
                    }
                }
            }

        }
    }
}

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

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

Xcodeの「Please reconnect the device.」の解決方法

実機のOSのバージョンとXcodeのバージョンばあっていない時に、発生するエラー。わかりにくいです!!

対応方法リスト

  1. Xcodeをアップデートする
  2. 現在のXcodeのバージョンに、実機のiOSに対応するDevice Supportを突っ込む(ここら辺が参考になる)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcodeで突然UIに青い枠線が表示される

突然各UIパーツに青い枠線が付き、解除するのに意外と時間かかったのでメモ。

バージョン

  • MacOS v10.15.6
  • Xcode v11.7

状況

storyboardでUIパーツを並べていたら、いきなりこんな青い枠線が表示されるようになった。

image.png

解決方法

Editor > Canvas > Bounds Rectanglesをオンにしたりオフにしたりする。
スクリーンショット 2020-09-13 23.53.59.png
勝手にオンになる関係で、UI上のBounds RectangleのON/OFFと実際の設定が噛み合っていない場合があり、Bounds Rectanglesに✅が入っていない場合にも枠線が表示されることがあるので、ON/OFFを何回か試してみて欲しい。

こうすると以下のように青い枠線が表示されないようになる。
image.png

原因

結論から言うと、”これだ!!”という原因は分からなかった。

ショートカット暴発説

何らかのショートカットを行う際に誤って暴発してしまったのではないか、と思い調べてみたがKey Bindingsにショートカットは登録されていなかった。
image.png

UIをいっぱいいじると勝手にトグルされる説

Stack Overflowの記事を見てみると、このようなコメントがあった。

I have found that this option toggles itself on automatically sometimes, like when you're moving lots of elements around the screen and an operation times out (which seems to happen frequently with just ~20 elements selected).

適当に訳すと、20個程度の要素を動かすと自動的にオンになることがあるらしい(自分の場合、そんなに動かしてないけど)

結論

現時点では、表示されたらBounds Rectanglesをオンにしたりオフにしたりするのが良さそう。
ちなみに再起動でも治るらしい。

参考文献

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