20201014のSwiftに関する記事は11件です。

swift実践入門output Chapter4 コレクションを表す型

CollectionとしてのString型

String型は単一の文字を表すCharacter型のコレクションとして定義されており、文字の列挙や文字数のカウントなどの機能を持つ

chapter4.swift
//StringIndex型 文字列内の位置を表すかた
let string = "abcdefghijelmnopqustuvwxyza"
let startString = string[string.startIndex]
print(startString)

//a

//n番目、最後のindexを取得するには
let string2 = "abcdefghijelmnopqustuvwxyza"
let startString2 = string2.startIndex

let bIndex = string.index(startString2, offsetBy: 8)
print(bIndex)
let bb = string[bIndex]
print(bb)

//i

let endString = string2.endIndex
let cIndex = string.index(endString, offsetBy: -1)
let cc = string[cIndex]
print(cc)

//countプロパティを用いて要素数を取得
print(string2.count)
//27

シーケンス 

その要素に一方向から順次アクセス可能なデータ構造

コレクションはシーケンスを包括する概念
一方向からの順次アクセスと特定のインデックスの値へ直接アクセスが可能なデータ構造

Sequenceプロトコル 要素へ順次アクセス

chapter4.swift
//forEach(_:)メソッド 要素に対して順次アクセス

let darray = [1,2,3,4,5,6,7]
var enumerated = [] as [Int]
darray.forEach ({ element in enumerated.append(element)
})

print(enumerated)

//[1, 2, 3, 4, 5, 6, 7]

//filterd(_:)メソッド 要素を絞り込む
let filtered = darray.filter ({ element  in element % 2 == 0
})
print(filtered)
//[2, 4, 6]

//map(_:)メソッド 要素を変換する
//全ての要素を特定の要素を用いて変換
let double = darray.map({element in element * 2})
print(double)
//[2, 4, 6, 8, 10, 12, 14]


//別の型のシーケンスへと変換 Int → String

let farray = [1,2,3,4,5]
let converted = farray.map({element in String(element)})
print(converted)

//["1", "2", "3", "4", "5"]


//flatMap(_:)メソッド 要素をシーケンスに変換し、それを一つのシーケンスに追加

let sss = [1,2,3]
let ttt = sss.flatMap({value in [value, value * 2]})
print(ttt)

//compacttMap(_:)メソッド 要素を、失敗する可能sるのある処理を用いて変換する

//全ての要素を特定の処理で変換するが、変換できない値は無視する

let ggg = ["abc","123","kjg","456"]
let integers = ggg.compactMap({value in Int(value)})
print(integers)
//[123, 456]


//reduce(_:)メソッド 要素を1つの値にまとめる
let sarray = [1,2,3,4,5,6,7,8,9]

let sum = sarray.reduce(0, {result, element in result + element} )
print(sum)
//45

let concat = sarray.reduce("", {result, element in result + String(element)})
print(concat)

//123456789




//Collectionプロトコル サブスクリプトによる要素へのアクセス
let warray = [1,2,2,3,2,1,2,3,4]

print(warray.count)
//9

print(warray.isEmpty)
//false

print(warray.first)
//Optional(1)

print(warray.last)
//Optional(4)

print(warray[3])
//3


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

swift実践入門output Chapter4 コレクションを表す型 後編

CollectionとしてのString型

String型は単一の文字を表すCharacter型のコレクションとして定義されており、文字の列挙や文字数のカウントなどの機能を持つ

chapter4.swift
//StringIndex型 文字列内の位置を表すかた
let string = "abcdefghijelmnopqustuvwxyza"
let startString = string[string.startIndex]
print(startString)

//a

//n番目、最後のindexを取得するには
let string2 = "abcdefghijelmnopqustuvwxyza"
let startString2 = string2.startIndex

let bIndex = string.index(startString2, offsetBy: 8)
print(bIndex)
let bb = string[bIndex]
print(bb)

//i

let endString = string2.endIndex
let cIndex = string.index(endString, offsetBy: -1)
let cc = string[cIndex]
print(cc)

//countプロパティを用いて要素数を取得
print(string2.count)
//27

シーケンス 

その要素に一方向から順次アクセス可能なデータ構造

コレクションはシーケンスを包括する概念
一方向からの順次アクセスと特定のインデックスの値へ直接アクセスが可能なデータ構造

Sequenceプロトコル 要素へ順次アクセス

chapter4.swift
//forEach(_:)メソッド 要素に対して順次アクセス

let darray = [1,2,3,4,5,6,7]
var enumerated = [] as [Int]
darray.forEach ({ element in enumerated.append(element)
})

print(enumerated)

//[1, 2, 3, 4, 5, 6, 7]

//filterd(_:)メソッド 要素を絞り込む
let filtered = darray.filter ({ element  in element % 2 == 0
})
print(filtered)
//[2, 4, 6]

//map(_:)メソッド 要素を変換する
//全ての要素を特定の要素を用いて変換
let double = darray.map({element in element * 2})
print(double)
//[2, 4, 6, 8, 10, 12, 14]


//別の型のシーケンスへと変換 Int → String

let farray = [1,2,3,4,5]
let converted = farray.map({element in String(element)})
print(converted)

//["1", "2", "3", "4", "5"]


//flatMap(_:)メソッド 要素をシーケンスに変換し、それを一つのシーケンスに追加

let sss = [1,2,3]
let ttt = sss.flatMap({value in [value, value * 2]})
print(ttt)

//compacttMap(_:)メソッド 要素を、失敗する可能sるのある処理を用いて変換する

//全ての要素を特定の処理で変換するが、変換できない値は無視する

let ggg = ["abc","123","kjg","456"]
let integers = ggg.compactMap({value in Int(value)})
print(integers)
//[123, 456]


//reduce(_:)メソッド 要素を1つの値にまとめる
let sarray = [1,2,3,4,5,6,7,8,9]

let sum = sarray.reduce(0, {result, element in result + element} )
print(sum)
//45

let concat = sarray.reduce("", {result, element in result + String(element)})
print(concat)

//123456789




//Collectionプロトコル サブスクリプトによる要素へのアクセス
let warray = [1,2,2,3,2,1,2,3,4]

print(warray.count)
//9

print(warray.isEmpty)
//false

print(warray.first)
//Optional(1)

print(warray.last)
//Optional(4)

print(warray[3])
//3


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

swift実践入門output Chapter4 コレクションを表す型

コレクションとは値の集まりのこと

配列を表す
Array< Element>型

辞書を表す
Dictionary< Key, Value>型

範囲を表す
Range< Bound>型

配列を表す Array< Element >型

実際にはArray< Int >型、Array< String >型のように使う

chapter4.swift
let a = [1,2,3]
let b = ["a","b","c"]

let array :[Int] = []

var strings = ["aaa","bbb","ccc"]
var strings1 = strings[0]
print(strings1)

//aaa

strings[2] = "gagaga"
print(strings)

["aaa", "bbb", "gagaga"]

//末尾に追加
strings.append("yamato")
print(strings)

//["aaa", "bbb", "gagaga", "yamato"]

//任意の位置に追加
strings.insert("mama", at: 2)
print(strings)

//["aaa", "bbb", "mama", "gagaga", "yamato"]


//削除は3タイプ
var integer = [1,2,3,4,5]
integer.remove(at: 2)
integer

integer.removeLast()
integer

integer.removeAll()
integer

辞書を表す Dictionary< Key, Value >型

chapter4.swift
let dictionary = ["Key":1]
let value = dictionary["key"]
print(value)

//["key": 1]

//変更
var dictionary1 = ["key": 2]
dictionary1["key"] = 1
print(dictionary1)
//追加
var dictionary2 = ["key":3]
dictionary2["key2"] = 4
print(dictionary2)

//["key2": 4, "key": 3]

//削除
var dictionary3 = ["key":5]
dictionary3["key"] = nil
print(dictionary3)

//[:]

範囲型 範囲を表す型

chapter4.swift
//末尾を含まない範囲 countableRange<Bound>型
let range = 1..<4
for value in range {
    print(value)
}

//1
//2
//3

//... 末尾を含む型 CountableClosedRange<Bound>型

let range2 = 1...4
for value in range2 {
    print(value)
}

//1
//2
//3
//4


let range3 = 1...5
print(range3.upperBound)
print(range3.lowerBound)

//5
//1


//値が範囲に含まれるかの判定

let range4 = 1..<10

print(range4.contains(4))
print(range4.contains(10))

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

swift実践入門output Chapter4 コレクションを表す型 前編

コレクションとは値の集まりのこと

配列を表す
Array< Element>型

辞書を表す
Dictionary< Key, Value>型

範囲を表す
Range< Bound>型

配列を表す Array< Element >型

実際にはArray< Int >型、Array< String >型のように使う

chapter4.swift
let a = [1,2,3]
let b = ["a","b","c"]

let array :[Int] = []

var strings = ["aaa","bbb","ccc"]
var strings1 = strings[0]
print(strings1)

//aaa

strings[2] = "gagaga"
print(strings)

["aaa", "bbb", "gagaga"]

//末尾に追加
strings.append("yamato")
print(strings)

//["aaa", "bbb", "gagaga", "yamato"]

//任意の位置に追加
strings.insert("mama", at: 2)
print(strings)

//["aaa", "bbb", "mama", "gagaga", "yamato"]


//削除は3タイプ
var integer = [1,2,3,4,5]
integer.remove(at: 2)
integer

integer.removeLast()
integer

integer.removeAll()
integer

辞書を表す Dictionary< Key, Value >型

chapter4.swift
let dictionary = ["Key":1]
let value = dictionary["key"]
print(value)

//["key": 1]

//変更
var dictionary1 = ["key": 2]
dictionary1["key"] = 1
print(dictionary1)
//追加
var dictionary2 = ["key":3]
dictionary2["key2"] = 4
print(dictionary2)

//["key2": 4, "key": 3]

//削除
var dictionary3 = ["key":5]
dictionary3["key"] = nil
print(dictionary3)

//[:]

範囲型 範囲を表す型

chapter4.swift
//末尾を含まない範囲 countableRange<Bound>型
let range = 1..<4
for value in range {
    print(value)
}

//1
//2
//3

//... 末尾を含む型 CountableClosedRange<Bound>型

let range2 = 1...4
for value in range2 {
    print(value)
}

//1
//2
//3
//4


let range3 = 1...5
print(range3.upperBound)
print(range3.lowerBound)

//5
//1


//値が範囲に含まれるかの判定

let range4 = 1..<10

print(range4.contains(4))
print(range4.contains(10))

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

SwiftでProperty List(.plist)への書き込みと読み込みを実装する

Property List(.plistファイル)へのデータ書き込みとデータ読み込みを、PropertyListEncoder(Decoder)Codableに準拠したモデルを利用して行います。

前提

Swift: 5.0

実装

モデルの用意

Property Listに保存したいデータのモデルを、Codableに準拠する形で作成します。
今回は例として、タイトルと著者名を要素に持つBookをモデルとします。

Book.swift
struct Book: Codable {
    var title: String
    var writerName: String
}

データの書き込み

ファイル操作はFileManagerを用いて行います。
先程用意したモデルを、PropertyListEncoderを使ってエンコードすることで、Property Listへの書き込みが行えます。

BookManager.swift
class BookManager {
    // 扱うProperty ListのURL Path
    static private var plistURL: URL {
        let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return documents.appendingPathComponent("book.plist")
    }

    static func write(book: Book) {
        let encoder = PropertyListEncoder()
        // 保存したいデータをエンコード
        guard let data = try? encoder.encode(book) else { return }
        // すでにProperty Listが存在する場合は上書き。そうでない場合は新しく作成
        if FileManager.default.fileExists(atPath: plistURL.path) {
            try? data.write(to: plistURL)
        } else {
            FileManager.default.createFile(atPath: plistURL.path, contents: data, attributes: nil)
        }
    }
}

データの読み込み

データの書き込みと同様に、ファイル操作はFileManagerを用いて行います。
PropertyListDecoderを用いてProperty Listから読み込んだデータをBookモデルにデコードしています。

BookManager.swift
class BookManager {
    // 省略 //

    static func load() -> Book {
        let decoder = PropertyListDecoder()
        // 保存先のProperty Listからデータを読み込んでデコード
        guard let data = try? Data.init(contentsOf: plistURL), 
              let book = try? decoder.decode(Book.self, from: data) else { 
              return Book(title: "", writerName: "")
        }
        return book
    }
}

終わりに

Codable及びPropertyListEncoder(Decoder)を使うことで、Property List(.plist)でのデータ操作を楽に実装することができました。

参考

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

Swift cornerRadiusのあれこれ

指定の角を丸くしたい

今回はUIImageについて触れる

任意のファイルに記載(わたしはUIパーツのextensionファイルを作成してそこに記載している)

///指定の角を指定の半径で丸くするメソッド
///- Parameters: 
///   - 半径
///   - 指定の角
 public func setCornerRadius(_ radius: CGFloat,_ corners: CACornerMask) {
    layer.cornerRadius = radius
    layer.masksToBounds = true
    layer.maskedCorners = corners
 }

使い方

///今回は全ての角に対して丸くする処理を行う。
let corners = [.layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner ]
view.setCornerRadius(15, corners)

以上。参考にしていただけると幸いです。

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

ヒラギノフォント使用時のUILabelの高さ調整

概要

iOS14にて、ヒラギノフォントを指定していたUILabelで、 g や jといった、
下にはみ出す文字の下部がはみ出すようになったので、その対応。

問題の詳しい解説などは以下などを参考にしてください
https://koze.hatenablog.jp/entry/2020/05/11/093000
http://akisute.com/2015/11/ios.html

環境

Swift5
Xcode11.3 (Xcode12.0でも動作確認)

解決作

*正直この対応で本当に問題がないのか、あまり自信はありません。間違いなどありましたら、ご指摘いただけますと幸いです??‍♀️

Extentionで関数使って修正している情報は見つかったのですが、
今回すでに問題が起こりうるヒラギノをつかった箇所がたくさんあり、ヒラギノフォントの設定はxibでやっていたため、
あまりコードはいじりたくないなあというところがあったので、カスタムクラスを作成して、そのカスタムクラスをxibで設定すれば、勝手に調整するようにしてみました。
調整の内容じたいは上でも紹介している以下と同じです。
http://akisute.com/2015/11/ios.html

import UIKit

class HiraginoUILabel: UILabel {

    override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        // ヒラギノフォントで日本語と英語が混じっている場合のラベルサイズずれ対応
        if let font = self.font,
            font.familyName.hasPrefix("Hiragino") { // 念の為ヒラギノフォントであること確認
            size.width = ceil(size.width);
            size.height = ceil(size.height + abs(font.descender));
        }
        return size;
    }
}

これで、xib側で、ヒラギノを指定しているLabelにカスタムクラスを指定すると、高さが自動で調節されます。
念の為カスタムクラス側でフォントを確認しているため、他のフォントに変わってクラス指定を外すことを忘れても、何もしないようにしています。
スクリーンショット 2020-10-14 13.16.08.png

以上です。

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

[iOS14]ヒラギノフォント使用時のUILabelの高さ調整

概要

iOS14にて、ヒラギノフォントを指定していたUILabelで、 g や jといった、
下にはみ出す文字の下部が見切れるようになったので、その対応。

↓下部が見切れている。
スクリーンショット 2020-10-16 15.40.10.png

問題の詳しい解説などは以下などを参考にしてください
https://koze.hatenablog.jp/entry/2020/05/11/093000
http://akisute.com/2015/11/ios.html

環境

Swift5
Xcode11.3 (Xcode12.0でも動作確認)

解決作

*正直この対応で本当に問題がないのか、あまり自信はありません。間違いなどありましたら、ご指摘いただけますと幸いです??‍♀️

Extentionで関数使って修正している情報は見つかったのですが、
今回すでに問題が起こりうるヒラギノをつかった箇所がたくさんあり、かつヒラギノの設定はxibでやっていたため、修正もxibできる方法を探しました。

サイズを調整するカスタムクラスを作成しました。
調整の内容自体は上でも紹介している以下の内容と同じです。
http://akisute.com/2015/11/ios.html

import UIKit

class HiraginoUILabel: UILabel {

    override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        // ヒラギノフォントで日本語と英語が混じっている場合のラベルサイズずれ対応
        if let font = self.font,
            font.familyName.hasPrefix("Hiragino") { // 念の為ヒラギノフォントであること確認
            size.width = ceil(size.width);
            size.height = ceil(size.height + abs(font.descender));
        }
        return size;
    }
}

これで、xib側で、ヒラギノを指定しているLabelにカスタムクラスを指定すると、高さが自動で調節されます。
念の為カスタムクラス側でフォントを確認しているため、他のフォントに変わってクラス指定を外すことを忘れても、何もしないようにしています。
スクリーンショット 2020-10-14 13.16.08.png

↓対応後
スクリーンショット 2020-10-16 15.40.15.png

以上です。

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

Swiftを使ってsinxの近似値を求める

なぜSwiftなの?

Swiftを使うために環境構築を行うが必要ないため,すぐに始められる。MacかiPadがあればPlaygroundsをインストールするだけで使うことができる。
また,Playgroundsにはステップ実行ゆっくり実行という機能があり,for文やwhile分をステップごとに実行し,実行されている時の値の変化を確認することもできる。
さらに,ビューア機能を使えば変数の変化の様子を勝手にグラフ表示してくれる。
2020-10-14 11.16のイメージ.jpg

sinxのマクローリン展開

$sinx$をマクローリン展開すると,
$$
sinx = x - \frac{x^3}{3!}+\frac{x^5}{5!}-\frac{x^7}{7!}+⋯+\frac{(-1)^{n-1}x^{2n-1}}{(2n-1)!}+⋯
$$

と表されるのであった。この記事では$sinx$の近似値をプログラミングを使って求めていく。

1.変数を用意する

マクローリン展開は何回でも微分できる関数$f(x)$を$x$の冪乗関数の級数の展開式の和で近似させるものであるのであった。この記事では$sinx$の値をsumとして,sumにだんだん値を加えていく。

var sum = 0
print(sum)    //0
sum += 1
print(sum)    //1

また,近似する$x$を入れる定数も用意しなければならない。この時近似する値として$\pi/6$や$\pi/3$などの値を用いたいので,$\pi$が使えるようimport Foundationを書いておく。

import Foundation
var sum = 0
let x = Double.pi/2

さらに,項として更新され続ける変数を用意する。

var item = x

第一項は定数と同じになるので,xを入れておく。

最終的なコードは以下のようになる。

2.x=π/2として,第二項までコーディングする

コーディングをする上で小さな値から始めることや具体的な値で試行錯誤することは重要だと思う。(個人の見解)。この記事でもその理念に則り$x=\pi/2$として第二項まで,つまり
$$
sinx=x-\frac{x^3}{3!}
$$
までコーディングする。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
var x = Double.pi/2

sum += x
sum += -(x*x*x)/Double(3*2*1)

sumに$x$を加え,次の行で$-\frac{x^3}{3!}$を加えている。

最後に真値と比較するprint文を加える。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
var x = Double.pi/2

sum += x
sum += -(x*x*x)/Double(3*2*1)

print(sum)//近似値0.9248322292886504
print(sin(Double.pi/2))//真値1.0

Double(3*2*1)としているのはInt型をDoule型に変換しているのだが,この記事では本質的な部分ではないので説明は割愛。

3.一般化しやすいように工夫する

さっきは第二項を-(x*x*x)/3*2*1としたが,これでは項数が増えるたびに$x$を増やさなければならない。項数が増えても短くコーディングすることができるように工夫を加える。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
var x = Double.pi/2

sum += x

for i in 2 ..< 3{
    x *= -(x*x)/Double((2*(i-1))*(2*i-1))
    sum += x
    print(x)
}

print(sum)//近似値0.9248322292886504
print(sin(Double.pi/2))//真値1.0

forを使い,項と変数iを対応させた。(1回しか実行されないが。)この場合第二項のみしか行わないので,for i in 2 ..< 3としている。一般化しているが,計算としては同じであるので2で出した答えと同じになるはずだ。

4.項数を増やす

一般化することによって項数を増やしても対応できる。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
let x = Double.pi/2
var item = x

sum += item

for i in 2 ..< 6{
    item *= -(x*x)/Double((2*(i-1))*(2*i-1))
    sum += item
}

print(sum)//近似値1.00000035425842861
print(sin(Double.pi/2))//真値1.0

第6項までの近似,つまり
$$
sinx= x-\frac{x^3}{3!}+\frac{x^5}{5!}-\frac{x^7}{7!}+\frac{x^9}{9!}-\frac{x^{11}}{11!}
$$
ではほとんど誤差が無いことがわかる。

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

[Swift5]"IBM Watson ToneAnalyzer"を使用して感情分析を行う

投稿のポイント

個人アプリ開発の機能としてIBM Watson ToneAnalyzerを用いて感情分析機能を実装したいので、私なりに公式ドキュメントを参考にしながら実装してみました。今回アウトプットするのは初期段階のAPIキーの取得と、実際に分析を行い、分析結果を表示するといったところです。

APIキーの取得

まず、Watson IBM Cloudのアカウントを作成します。下記urlから公式ページに遷移し、右上にある登録からアカウントを作成して下さい。
https://cloud.ibm.com/developer/watson/services
登録が完了したら下記画像のようにWatsonサービスのToneAnalyzeを選択してください。
image.png
続いて、リージョンを東京、プランのライトを選択して作成をクリックします。
image.png
すると専用ページに遷移するので資格情報の取得を選択し、APIキーURLを取得します。

この時点でAPIキーURLをプロジェクトに記述し、コメントアウトしておくことをオススメします!

ToneAnalyzerをインポート

APIキーの取得はできたので、ここからコードの実装に参りたいと思います。まず、Cocoapodsを使用してToneAnalyzerをインポートします。まだ、podsをインストールしたことがないという方は下記urlを参考にしてください。
https://qiita.com/ShinokiRyosei/items/3090290cb72434852460

それではpodのインストールを行います。

Podfile.
target 'アプリ名' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for アプリ名

pod 'IBMWatsonToneAnalyzerV3', '~> 3.6.0' #ここを記述

end

次に、フレームワークを使用したいコントローラーにインポートします。

ここで注意していただきたいのは、IBMWatsonプレフィックスとバージョンサフィックスを除外します。IBMWatsonToneAnalyzerV3の、IBMWatsonと、V3を削除してimport ToneAnalyzerと記述するということです。

APIの認証

フレームワークのインポートが完了したら次は認証フェーズです。スタンダートな方法として、IBM Cloud Identity and Access Management(IAM)を使用し、APIキーとAPIのversionとURLを定義します。

ViewController.swift
import UIKit
import ToneAnalyzer

class ViewController: UIViewController {

    //WatsonAPIキーを定義
    let authenticator = WatsonIAMAuthenticator(apiKey: "ここに取得したAPIキーを添付")

    override func viewDidLoad() {
        super.viewDidLoad()

        //WatsonAPIのversionとURLを定義
        let toneAnalyzer = ToneAnalyzer(version: "2017-09-21", authenticator: authenticator)
            toneAnalyzer.serviceURL = "ここに取得したURLを添付"
    }
}

ここで注意点ですが、serviceURLは APIを作成した時に設定した場所によって変化します。下記に列挙しているので、設定した場所に合わせてtoneAnalyzer.serviceURLに代入してあげてください。

場所別のサービスURL

     ダラス: https://api.us-south.tone-analyzer.watson.cloud.ibm.com
 ワシントンDC: https://api.us-east.tone-analyzer.watson.cloud.ibm.com
フランクフルト: https://api.eu-de.tone-analyzer.watson.cloud.ibm.com
   ロンドン: https://api.eu-gb.tone-analyzer.watson.cloud.ibm.com
    ソウル: https://api.kr-seo.tone-analyzer.watson.cloud.ibm.com
    シドニー: https://api.au-syd.tone-analyzer.watson.cloud.ibm.com
    Tokyo: https://api.jp-tok.tone-analyzer.watson.cloud.ibm.com

エラー処理とデータ処理

続いてエラー処理とデータ処理を記述します。と、その前に分析に使用するサンプルテキストを宣言します。

ViewController.swift
import UIKit
import ToneAnalyzer

class ViewController: UIViewController {

    //WatsonAPIキーを定義
    let authenticator = WatsonIAMAuthenticator(apiKey: "ここに取得したAPIキーを添付")

    //分析用サンプルテキスト
    let sampleText = """
    Team, I know that times are tough! Product \
    sales have been disappointing for the past three \
    quarters. We have a competitive product, but we \
    need to do a better job of selling it!
    """

    override func viewDidLoad() {
        super.viewDidLoad()

        //WatsonAPIのversionとURLを定義
        let toneAnalyzer = ToneAnalyzer(version: "2017-09-21", authenticator: authenticator)
            toneAnalyzer.serviceURL = "ここに取得したURLを添付"
    }
}

それでは定義したsampleTextを使って分析を行います。switch文で条件分岐を行い、ステータスコードによって表示される内容を変更します。ステータスコードの200範囲は成功、400範囲は障害、500範囲は内部システムエラーを表しております。

ViewController.swift
import UIKit
import ToneAnalyzer

class ViewController: UIViewController {

    //WatsonAPIキーを定義
    let authenticator = WatsonIAMAuthenticator(apiKey: "q6GL14WCXtIbNgwYazVmBDNGlyd3jmxglni-pmk96g0z")

    //分析用サンプルテキスト
    let sampleText = """
    Team, I know that times are tough! Product \
    sales have been disappointing for the past three \
    quarters. We have a competitive product, but we \
    need to do a better job of selling it!
    """

    override func viewDidLoad() {
        super.viewDidLoad()

        //WatsonAPIのversionとURLを定義
        let toneAnalyzer = ToneAnalyzer(version: "2017-09-21", authenticator: authenticator)
            toneAnalyzer.serviceURL = "https://api.jp-tok.tone-analyzer.watson.cloud.ibm.com"

        //エラー処理
        toneAnalyzer.tone(toneContent: .text(sampleText)){ #ここでsampleTextを定義
          response, error in
          if let error = error {
            switch error {
            case let .http(statusCode, message, metadata):
              switch statusCode {
              case .some(404):
                // Handle Not Found (404) exceptz1zion
                print("Not found")
              case .some(413):
                // Handle Request Too Large (413) exception
                print("Payload too large")
              default:
                if let statusCode = statusCode {
                  print("Error - code: \(statusCode), \(message ?? "")")
                }
              }
            default:
              print(error.localizedDescription)
            }
            return
          }
          //データ処理
          guard let result = response?.result else {
            print(error?.localizedDescription ?? "unknown error")
            return
          }
          print(result)
          //ステータスコードの表示(200範囲は成功、400範囲は障害、500範囲は内部システムエラー)
          print(response?.statusCode as Any)
          //ヘッダーパラメータ
          print(response?.headers as Any)
        }
    }
}

実行

ここまで記述することができたらビルドを行い、実際に分析情報を引っ張ってこれるかの確認を行います。
デバックエリアに下記のように分析結果が表示されれば成功です。

ToneAnalysis(documentTone: ToneAnalyzer.DocumentAnalysis(tones: Optional([ToneAnalyzer.ToneScore(score: 0.6165, toneID: "sadness", toneName: "Sadness"), ToneAnalyzer.ToneScore(score: 0.829888, toneID: "analytical", toneName: "Analytical")]), toneCategories: nil, warning: nil), sentencesTone: Optional([ToneAnalyzer.SentenceAnalysis(sentenceID: 0, text: "Team, I know that times are tough!", tones: Optional([ToneAnalyzer.ToneScore(score: 0.801827, toneID: "analytical", toneName: "Analytical")]), toneCategories: nil, inputFrom: nil, inputTo: nil), ToneAnalyzer.SentenceAnalysis(sentenceID: 1, text: "Product sales have been disappointing for the past three quarters.", tones: Optional([ToneAnalyzer.ToneScore(score: 0.771241, toneID: "sadness", toneName: "Sadness"), ToneAnalyzer.ToneScore(score: 0.687768, toneID: "analytical", toneName: "Analytical")]), toneCategories: nil, inputFrom: nil, inputTo: nil), ToneAnalyzer.SentenceAnalysis(sentenceID: 2, text: "We have a competitive product, but we need to do a better job of selling it!", tones: Optional([ToneAnalyzer.ToneScore(score: 0.506763, toneID: "analytical", toneName: "Analytical")]), toneCategories: nil, inputFrom: nil, inputTo: nil)]))
Optional(200)
Optional(["x-service-build-number": "2020-10-12T05:02:12", "x-dp-watson-tran-id": "e7f11a38-2089-4860-8414-7f1e3878f449", "Content-Security-Policy": "default-src \'none\'", "x-xss-protection": "1; mode=block", "x-service-api-version": "null; 2017-09-21", "x-global-transaction-id": "e7f11a38-2089-4860-8414-7f1e3878f449", "Date": "Wed, 14 Oct 2020 01:20:56 GMT", "Server": "watson-gateway", "x-powered-by": "Servlet/3.1", "X-EdgeConnect-Origin-MEX-Latency": "212", "Content-Language": "en-US", "x-request-id": "e7f11a38-2089-4860-8414-7f1e3878f449", "Access-Control-Allow-Origin": "*", "Content-Length": "726", "Cache-Control": "no-store", "Content-Type": "application/json", "Pragma": "no-cache", "Connection": "keep-alive", "Strict-Transport-Security": "max-age=31536000; includeSubDomains;", "X-EdgeConnect-MidMile-RTT": "7", "x-content-type-options": "nosniff"])

ViewController.swiftで記述したprintの内容がしっかりと表示されていますね。
ステータスコードも(200)と表示されているので成功ということです。

ViewController.swift
print(result)
//ステータスコードの表示(200範囲は成功、400範囲は障害、500範囲は内部システムエラー)
print(response?.statusCode as Any)
//ヘッダーパラメータ
print(response?.headers as Any)

これで実際にアプリケーションに組み込む実装はまだですが、初期段階のAPIキーの取得と、実際に分析を行い、分析結果を表示するという点に関して実装できました!

最後に

引き続き個人アプリの開発に取り組みながらアウトプットを行いますので、参考にしていただけると幸いです。最後までご覧いただきありがとうございました!

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

【Swift5】Firebaseで会員登録の実装

はじめに

Firebaseとチャット機能の学習としてLINEのクローンアプリを作成しているのですが、その際のFirebaseを用いた会員登録の実装を備忘録として投稿します。
初学者ですので訂正点ございましたら、ご指摘よろしくお願いします。

概要

今回、会員登録機能を実装するにあたり下記のように順序立てしました。
1.FirebaseAuthへユーザー登録
2.FirebaseStorageへプロフィール画像を登録
3.FirebaseFirestoreへユーザー情報を登録

  • メールアドレスパスワードユーザーネームの全てに記入がされていれば登録処理可(新規登録ボタン有効)としています。
  • 1.の処理の際に、承認メールを使い会員登録を完了させてから2.の処理に移るのがよくある手法かと思われますが、今回の目的が学習のためでしたのでその辺りは考慮しておりません。

実行環境

【Xcode】Version 12.0.1
【Swift】Version 5.3
【CocoaPods】version 1.9.3
【Firebase】version 6.29.0

実装後の画面

sample.gif

実装コード

SignUpModel.swift
import Foundation
import Firebase

//delegateはweak参照したいため、classを継承する
protocol SignUpModelDelegate: class {
    func createImageToFirestorageAction()
    func createUserToFirestoreAction(fileName: String?)
    func completedRegisterUserInfoAction()
}

class SignUpModel {

    // delegateはメモリリークを回避するためweak参照する
    weak var delegate: SignUpModelDelegate?

    func createUser(email: String, password: String) {
        // FirebaseAuthへ保存
        Auth.auth().createUser(withEmail: email, password: password) { (res, err) in
            if let err = err {
                print("FirebaseAuthへの保存に失敗しました。\(err)")
                // ユーザー情報の登録が失敗した時の処理
                return
            }
            print("FirebaseAuthへの保存に成功しました。")
            // FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
            self.delegate?.createImageToFirestorageAction()
        }
    }

    func creatrImage(fileName: String, uploadImage: Data) {
        // FirebaseStorageへ保存
        let storageRef = Storage.storage().reference().child("profile_image").child(fileName)
        storageRef.putData(uploadImage, metadata: nil) { (metadate, err) in
            if let err = err {
                print("Firestorageへの保存に失敗しました。\(err)")
                // ユーザー情報の登録が失敗した時の処理
                return
            }
            print("Firestorageへの保存に成功しました。")
            // FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
            self.delegate?.createUserToFirestoreAction(fileName: fileName)
        }
    }

    func createUserInfo(uid: String, docDate: [String : Any]) {
        // FirebaseFirestoreへ保存
        Firestore.firestore().collection("users").document(uid).setData(docDate as [String : Any]) { (err) in
            if let err = err {
                print("Firestoreへの保存に失敗しました。\(err)")
                // ユーザー情報の登録が失敗した時の処理
                return
            }
            print("Firestoreへの保存に成功しました。")
            // ユーザー情報の登録が完了した時の処理
            self.delegate?.completedRegisterUserInfoAction()
        }
    }

}

SignUpViewController.swift
import UIKit
import Firebase
import FirebaseStorage
import IQKeyboardManagerSwift

class SignUpViewController: UIViewController {

    @IBOutlet weak var profileImageButton: UIButton!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var userNameTextField: UITextField!
    @IBOutlet weak var signUpButton: UIButton!

    let signUpModel = SignUpModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        IQKeyboardManager.shared.enable = true

        emailTextField.delegate = self
        passwordTextField.delegate = self
        userNameTextField.delegate = self
        signUpModel.delegate = self

        // 画面UIについての処理
        setupUI()

    }

    // 画面UIについての処理
    func setupUI() {
        signUpButton.layer.cornerRadius = 3
        signUpButton.isEnabled = false
        profileImageButton.layer.masksToBounds = true
        profileImageButton.layer.cornerRadius = 75
        profileImageButton.layer.borderColor = UIColor.lightGray.cgColor
        profileImageButton.layer.borderWidth  = 0.1
    }

    // プロフィール画像の選択(フォトライブラリーへ遷移)
    @IBAction func profileImageButtonAction(_ sender: Any) {
        let imagePickerController = UIImagePickerController()
        imagePickerController.allowsEditing = true
        imagePickerController.delegate = self
        self.present(imagePickerController, animated: true, completion: nil)
    }

    // 新規登録処理
    @IBAction func signUpButtonAction(_ sender: Any) {

        guard let email = emailTextField.text,
              let password = passwordTextField.text
        else { return }

        // FirebaseAuthへ保存
        signUpModel.createUser(email: email, password: password)      
    }

    # ・・・省略・・・

    // プロフィール画像をFirebaseStorageへ保存する処理
    private func createImageToFirestorage() {
        // プロフィール画像が設定されている場合の処理
        if let image = self.profileImageButton.imageView?.image {
            let uploadImage = image.jpegData(compressionQuality: 0.5)
            let fileName = NSUUID().uuidString
            // FirebaseStorageへ保存
            signUpModel.creatrImage(fileName: fileName, uploadImage: uploadImage!) 
        } else {
            print("プロフィール画像が設定されていないため、デフォルト画像になります。")
            // User情報をFirebaseFirestoreへ保存
            self.createUserToFirestore(profileImageName: nil)
        } 
    }

    // User情報をFirebaseFirestoreへ保存する処理
    private func createUserToFirestore(profileImageName: String?) {

        guard let email = Auth.auth().currentUser?.email,
              let uid = Auth.auth().currentUser?.uid,
              let userName = self.userNameTextField.text
        else { return }

        // 保存内容を定義する(辞書型)
        let docData = ["email": email,
                       "userName": userName,
                       "profileImageName": profileImageName,
                       "createdAt": Timestamp()] as [String : Any?]

        // FirebaseFirestoreへ保存
        signUpModel.createUserInfo(uid: uid, docDate: docData as [String : Any])
    }

}

extension SignUpViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    // 写真が選択された時に呼ばれるメソッド
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let editedImage = info[.editedImage] as? UIImage {
            profileImageButton.setImage(editedImage.withRenderingMode(.alwaysOriginal), for: .normal)
        } else if let originalImage = info[.originalImage] as? UIImage {
            profileImageButton.setImage(originalImage.withRenderingMode(.alwaysOriginal), for: .normal)
        }
        dismiss(animated: true, completion: nil)
    }

}

extension SignUpViewController: UITextFieldDelegate {
    // textFieldでテキスト選択が変更された時に呼ばれるメソッド
    func textFieldDidChangeSelection(_ textField: UITextField) {
        // textFieldが空かどうかの判別するための変数(Bool型)で定義
        let emailIsEmpty = emailTextField.text?.isEmpty ?? true
        let passwordIsEmpty = passwordTextField.text?.isEmpty ?? true
        let userNameIsEmpty = userNameTextField.text?.isEmpty ?? true
        // 全てのtextFieldが記入済みの場合の処理
        if emailIsEmpty || passwordIsEmpty || userNameIsEmpty {
            signUpButton.isEnabled = false
            signUpButton.backgroundColor = UIColor.systemGray2
        } else {
            signUpButton.isEnabled = true
            signUpButton.backgroundColor = UIColor(named: "lineGreen")
        }
    }

    // textField以外の部分を押したときキーボードが閉じる
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }
}

extension SignUpViewController: SignUpModelDelegate {

    // FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
    func createImageToFirestorageAction() {
        print("FirebaseAuthへの保存に成功しました。")
        self.createImageToFirestorage()
    }

    // FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
    func createUserToFirestoreAction(fileName: String?) {
        print("Firestorageへの保存に成功しました。")
        self.createUserToFirestore(profileImageName: fileName)
    }

    // ユーザー情報の登録が完了した時の処理
    func completedRegisterUserInfoAction() {
        // ChatListViewControllerへ画面遷移
        let storyboard = UIStoryboard(name: "ChatList", bundle: nil)
        let chatListVC = storyboard.instantiateViewController(withIdentifier: "ChatListVC") as! ChatListViewController
        let nav = UINavigationController(rootViewController: chatListVC)
        nav.modalPresentationStyle = .fullScreen
        nav.modalTransitionStyle = .crossDissolve
        self.present(nav, animated: true, completion: nil)
    }

}

Firebaseに関しての処理にフォーカスを当てていますので下記項目の説明は省かせていただきます。

  • エラー処理について
  • UIActivityIndicatorViewについて
  • UIImagePickerControllerについて
  • IQKeyboardManagerSwiftについて(詳細はこちら参照ください。)

準備

①前提条件

下記項目が完了しているのを前提条件としまして進めていきます。

  • Firebaseプロジェクトの作成
  • XcodeでのFirebaseSDK組み込み

このあたりがまだという方は、ドキュメントを参照よろしくお願いします。

②Xcode側の準備

Podfileに以下を追加して、ターミナルでpod installをします。

  pod 'Firebase/Analytics'
  pod 'Firebase/Auth'
  pod 'Firebase/Core'
  pod 'Firebase/Firestore'
  pod 'Firebase/Storage'
  pod 'FirebaseUI/Storage'

③Firebase側の準備

スクリーンショット 2020-10-12 20.15.13.png
上図のようにSign-in methodタブからメール/パスワードを選択します。鉛筆のアイコンで編集画面を開きます。

スクリーンショット 2020-10-12 20.15.21.png
有効にしたら保存します。準備としてはこれで終わりになります。

実装詳細①(FirebaseAuth編)

SignUpViewController.swift
// 新規登録処理
@IBAction func signUpButtonAction(_ sender: Any) {

    guard let email = emailTextField.text,
          let password = passwordTextField.text
    else { return }

    // FirebaseAuthへ保存
    signUpModel.createUser(email: email, password: password)      
}
SignUpModel.swift
func createUser(email: String, password: String) {
    // FirebaseAuthへ保存
    Auth.auth().createUser(withEmail: email, password: password) { (res, err) in
        if let err = err {
            print("FirebaseAuthへの保存に失敗しました。\(err)")
            // ユーザー情報の登録が失敗した時の処理
            return
        }
        print("FirebaseAuthへの保存に成功しました。")
        // FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
        self.delegate?.createImageToFirestorageAction()
    }
}

実装詳細②(FirebaseStorage編)

SignUpViewController.swift
// FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
func createImageToFirestorageAction() {
    print("FirebaseAuthへの保存に成功しました。")
    self.createImageToFirestorage()
}
SignUpViewController.swift
// プロフィール画像をFirebaseStorageへ保存する処理
private func createImageToFirestorage() {
    // プロフィール画像が設定されている場合の処理
    if let image = self.profileImageButton.imageView?.image {
        // 画像を圧縮
        let uploadImage = image.jpegData(compressionQuality: 0.5)
        // ユニークなIDを取得
        let fileName = NSUUID().uuidString
        // FirebaseStorageへ保存
        signUpModel.creatrImage(fileName: fileName, uploadImage: uploadImage!) 
    } else {
        print("プロフィール画像が設定されていないため、デフォルト画像になります。")
        // User情報をFirebaseFirestoreへ保存
        self.createUserToFirestore(profileImageName: nil)
    } 
}
SignUpModel.swift
func creatrImage(fileName: String, uploadImage: Data) {
    // FirebaseStorageへ保存
    let storageRef = Storage.storage().reference().child("profile_image").child(fileName)
    storageRef.putData(uploadImage, metadata: nil) { (metadate, err) in
        if let err = err {
            print("Firestorageへの保存に失敗しました。\(err)")
            // ユーザー情報の登録が失敗した時の処理
            return
        }
        print("Firestorageへの保存に成功しました。")
        // FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
        self.delegate?.createUserToFirestoreAction(fileName: fileName)
    }
}

実装詳細③(FirebaseFirestore編)

SignUpViewController.swift
// FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
func createUserToFirestoreAction(fileName: String?) {
    print("Firestorageへの保存に成功しました。")
    self.createUserToFirestore(profileImageName: fileName)
}
SignUpViewController.swift
// User情報をFirebaseFirestoreへ保存する処理
private func createUserToFirestore(profileImageName: String?) {

    guard let email = Auth.auth().currentUser?.email,
          let uid = Auth.auth().currentUser?.uid,
          let userName = self.userNameTextField.text
    else { return }

    // 保存内容を定義する(辞書型)
    let docData = ["email": email,
                   "userName": userName,
                   "profileImageName": profileImageName,
                   "createdAt": Timestamp()] as [String : Any?]

    // FirebaseFirestoreへ保存
    signUpModel.createUserInfo(uid: uid, docDate: docData as [String : Any])
}
SignUpModel.swift
func createUserInfo(uid: String, docDate: [String : Any]) {
    // FirebaseFirestoreへ保存
    Firestore.firestore().collection("users").document(uid).setData(docDate as [String : Any]) { (err) in
        if let err = err {
            print("Firestoreへの保存に失敗しました。\(err)")
            // ユーザー情報の登録が失敗した時の処理
            return
        }
        print("Firestoreへの保存に成功しました。")
        // ユーザー情報の登録が完了した時の処理
        self.delegate?.completedRegisterUserInfoAction()
    }
}
SignUpViewController.swift
// ユーザー情報の登録が完了した時の処理
func completedRegisterUserInfoAction() {
    // ChatListViewControllerへ画面遷移
    # ・・・省略・・・
}

参考

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