20201208のSwiftに関する記事は16件です。

演算子について【Swift】

はじめに

初歩的な演算子についての備忘録になります。

比較演算子

 例
等しい a == b
等しくない a != b
大なり a > b
小なり a < b
より以上 a >= b
より以下 a <= b

論理演算子

 例
論理否定  !a
論理積 a && b
論理和 a ll b

複合代入演算子

変数の値を更新したいときに使う(整数のみ)

 例  内容
+= a += b a = a + b
-= a -= b a = a - b
*= a *= b a = a * b
/= a /= b a = a / b
%= a %= b a = a % b

自然数列の和を求める例

var sum = 0
for i in 1...100 {
    sum += i
}
print(sum) // 5050

三項演算子

簡単な場合分けによる値代入

書式

条件 ? trueの値 : falseの値

簡単な場合分けの例

let a = 30
let b = 60
var winner = "勝者は"
// if文での条件分岐
if a > b {
    winner += "Aさん"
} else {
    winner += "Bさん"
}
print(winner) // 勝者はBさん
let a = 30
let b = 60
var winner = "勝者は"
// 三項演算子
winner += a > b ? "Aさん" : "Bさん"
print(winner) // 勝者はBさん
  • 5行が1行にまとまりスッキリとなりました。
  • 三項演算子の気をつける点としまして、半角スペースを置かずに「?」をつけると「オプショナル型の宣言」と解釈されエラーになりますのでご注意下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】これでワカッタ!なるほど!オプショナル型ってそーゆーこと

というわけで

つづき。
前回はこれを見てね。

【Swift】オプショナル型の何が嬉しいのか
https://qiita.com/antk/items/2d780f152c26a3733109

今回はこのオプショナル型を活用してみる。

ラップ

ラップとは?
とりあえず置いといて、前回「nilが使える」ということで定義したオプショナル型。

var hoge: String?

この hoge というオプショナル型の変数そのままではいじることができない
どういうことか?
上記のように文字列型(String)だとわかりにくいので、例えば

var number: Int? = 2

という数値型(Int)のオプショナル型の変数を定義して、こいつに 2 を入れている。例えばこれを出力すると

print(number)

// 出力 Optional(2)

という結果が出る。
逆に、 ? を付けない非オプショナル型だと

var number: Int = 2

print(number)

// 出力 2

さっきの Optional が付いていない、ただの 2 が出力される
非オプショナル型と違い、オプショナル型で定義されているのはただの 2 じゃないのである。
paiza.ioとかで試してみるといい。
これはなんなのかと言うと、 Optional という殻、カプセル、シェルター、バリアー……まあなんでもいいが、オプショナル型は要素がそういうものに入っていると考える。
これを『ラップ(wrap)されている』という。
ラップされている numberに入っているので

var number: Int? = 2

print(number + 4)

// エラー

このように数字を足して 6 にしようとしても Optionatl の殻に弾かれてしまうのでうまくいかないのである。
これをやるには殻を解く、アンラップという作業が必要になる。

アンラップ

オプショナル型変数の後ろに ! を付ける。

var number: Int? = 2

print(number! + 4)

// 出力 6

こうすることで望みの結果が出た。
全然関係ないがデーモン小暮閣下は昔『!(エクスクラメイション)』という名義でアルバムを出していた。

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

AtCoder過去問を使って新人にSwiftを練習させてみると良かった

はじめに

チームに入ってきた新人が、Swift/iOS開発をN予備校などでやったものの、
関数もかけない状況というのが判明しました。

AtcoderでSwiftを練習させてみたので振り返りです。

訓練のフォーマット

  1. A問題を5問とく。B問題を1問とく。
  2. 全部完答したら、ACした他の人の回答をみる。初めてみるAPIを調べる。
  3. 自チームのプロジェクトでそのAPIを全文検索して、何をしているか議論してみる。

A問題を5問とく、B問題を1問とく

A問題を説いていくことで、程よい達成感を得てもらい、慣れたらB問題で少し考える問題にも挑戦してもらいます。

問題を説いてSwiftをガシガシ書いてもらいたいというフェーズです。
やってない問題はAtCoder Problemsで確認します。

全部完答したら、ACした他の人の回答をみる。初めてみるAPIを調べる。

他の人の回答が見られるのは、AtCoderの良さの1つだと思います。
提出結果の全ての提出からSwiftを選択して見ていきます。(Swiftは人少ない!w) 

スクリーンショット 2020-12-08 20.26.46.png

自分の書いた答えと、他の人が書いたコードを比べてみてもらいます。

その中で、自分の見たことのないAPIを調べてもらいます。(僕が説明したものもあります)
新人が見たことのないやつというのは、mapfilterreduceなどです。

自チームのプロジェクトでそのAPIを全文検索して、何をしているか議論してみる。

学んだAPIがちゃんとプロジェクトでも使われているよ!ということを知ってもらうために検索してもらいました。
使い方やコードリーディングの練習のためです。

ここはどのように使われているかなど解説したり議論しました。

AtCoderでの落とし穴

Swiftが最初のA - Welcome to AtCoderという問題の例にありません!

初心者が解くのは難しいので、以下のコードを教えてあげる必要があります。
[AtCoder]Swiftでも競プロがしたい! - Qiitaより引用

// 標準入力からIntを読み込む
let a = Int(readLine()!)!

// 標準入力からInt配列を読み込む
let bc = readLine()!.split(separator: " ").map { Int($0)! }

// 標準入力から文字列を読み込む
let s = readLine()!

// bcは配列なので、bc[0]にb bc[1]にcが入っている
let sum = a + bc[0] + bc[1]

// スペース区切りで出力
print(sum, s)

AtCoderでの初心者教育のよかった点

  • ちょうどいい問題の大きさで、プログラミングで問題を解く楽しさがわかる
  • 他の人のコードを見て学べる
  • 業務外でも勉強するきっかけになる (ちゃんと勉強しているらしい!)
  • 仕様を読んで、実装する訓練になる

実務でも重要な点が詰まってますね!

おわりに

初心者がプログラミングの教本や入門動画を見ただけだと、いざ書いてみると全くできないことが多いと思います。

競技プログラミングでは、読んで、理解して、プログラムという流れが細かいリズムで体験できるので、プログラミングに慣れるのに適していると感じました。

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

複数のmlmodelを統一的に扱う

複数のmlmodelを統一的に扱う

はじめに

僕は機械学習系の研究室に所属している大学院生で、普段はPythonを用いてコードを書いています。また、Apple信者でもあるのでSwiftでアプリを作ることもあります。

SwiftにはCore ML1というApple製の機械学習ライブラリが存在します。Core MLは、mlmodelファイルをプロジェクトにドラッグ&ドロップするだけでモデルを使うためのコードが自動生成されるため、とても便利です。一方で、複数のモデルを扱うときに少し不便なことがあります。

機械学習系の研究をする際、複数の機械学習モデルを比較することが大いにあると思います。Pythonの場合は、

models = {
    "Random Forest": RandomForestClassifier(),
    "SVM": SVC(),
    "VGG16": ...
}

for model in models:
    model.fit()

    outputs = model.predict()
    ...

のようにして、辞書(もしくは配列)にしておくことでfor文で複数のモデルを実行することができます。

しかし、Swiftの場合、静的型付けであるため動的型付けであるPythonのように違うクラス・型のものを辞書や配列に入れて扱うのは難しいと思います。そこで今回は、複数のCore MLモデル(mlmodel)を比較したり切り替えたいというときに、コードを共通化する方法を記事にしたいと思います。

(今回は、Visionを用いたものではなくMLMultiArray2を入力とするCore MLモデルを想定しています)

環境

  • Xcode 12.2
  • macOS 11.0.1

mlmodelを統一的に扱う

プロトコルを作る

MyModel.mlmodelファイルをXcodeにドラッグ&ドロップすると自動的にSwiftのコードが生成され、MyModelInputMyModelOutputMyModelの3つのクラスができます。MyModelInputMyModelOutputMLFeatureProvider3というプロトコルに準拠していますが、MyModelMLModelを持っているだけで単体のクラスとして生成されます。

そこで、プロトコルを作成しMyModelを拡張して準拠させることで統一したルールで扱うことができるようにします。また、生成されたコードのpredictionの戻り値はMyModelOutputでありmlmodelごとに異なるため、predictionの戻り値も統一的に扱えるようにします。今回は戻り値をStringにしましたが、クラスラベルの列挙型を作成しても良いと思います。

protocol MLModelUnification {
    func prediction(input: MLMultiArray) throws -> String
    func predictions(inputs: [MLMultiArray]) throws -> [String]
}

モデルのクラスを拡張する

次に作成したプロトコルをモデルのクラスに準拠させるためにモデルのクラスを拡張します。

VGG16.mlmodelResNet50.mlmodelの2つのモデルがあった場合の例を示します。2つのモデルの出力にあたるVGG16OutputResNet50Outputは、

  • classLabel: String: 予測ラベル
  • Identity: [String : Double]: 各ラベルの予測確率

を持っているとします。

VGG16のextension
extension VGG16: MLModelUnification {
    func prediction(input: MLMultiArray) throws -> String {
        let output = try self.prediction(input: VGG16Input(input: input))
            return output.classLabel
    }

    func predictions(inputs: [MLMultiArray]) throws -> [String] {
        var results: [String] = []
        try inputs.forEach { (input) in
                let output = try self.prediction(input: VGG16Input(input: input))
                results.append(output.classLabel)
            }
            return results
    }
}
ResNet50のextension
extension ResNet50: MLModelUnification {
    func prediction(input: MLMultiArray) throws -> String {
        let output = try self.prediction(input: ResNet50Input(input: input))
            return output.classLabel
    }

    func predictions(inputs: [MLMultiArray]) throws -> [String] {
        var results: [String] = []
        try inputs.forEach { (input) in
                let output = try self.prediction(input: ResNet50Input(input: input))
                results.append(output.classLabel)
            }
            return results
    }
}

複数のモデルで共通のコードを使うことができる

このようにプロトコルを作成しそれぞれのモデルのクラスを準拠させることで、予測部分のコードを共通化することができます。

共通化の例(モデルを比較)
var models: [MLModelUnification] = [
            {
                do {
                    return try VGG16(configuration: config)
                } catch {
                    fatalError("Couldn't create VGG16")
                }
            }(),
            {
                do {
                    return try ResNet18(configuration: config)
                } catch {
                    fatalError("Couldn't create ResNet 18")
                }
            }()
]

for model in models {
    let outputs = try model.prediction(inputs: inputs)
    ...
}
共通化の例(モデルの切り替え)
var model: MLModelUnification

// モデルを列挙したenumでswitch文
switch selectedModel {
 case .vgg16:
    model_ = {
        do {
            return try VGG16(configuration: config)
        } catch {
            print(error)
            fatalError("Couldn't create VGG16")
        }
    }()
case .resnet18:
    model_ = {
        do {
            return try ResNet18(configuration: config)
        } catch {
            print(error)
            fatalError("Couldn't create ResNet 18")
        }
    }()
}

let output = try model.prediction(input: input)
...

おわりに

今回は、Core MLモデル(mlmodel)で生成される機械学習モデルのクラスを統一的に扱う方法についてまとめました。個人的に、Swiftの便利なところの1つはextensionだなぁと思いました。同じようなことをしようとしている記事を見かけなかったので、CoreML上では複数のモデルを切り替えたり比較することがあまりないかも知れませんが、もし同じようなことがしたい方がいれば参考にしてくれると嬉しいです。

M1 Mac mini欲しさに、「ちょっとした工夫で効率化!【PR】パソナテック Advent Calendar 2020」に向けて記事を書きました。初めてQiita記事を書いたため、拙い部分もあるかと思いますが、ここまで読んでくれてありがとうございました。

参考記事

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

繰り返し文について〜while文〜

while文 とは

while文は、条件式が成り立つ限り繰り返される制御構文になります。

while文は条件式と繰り返し実行される文から成り立ちます。

while 条件式 {
   条件式が成立する間繰り返される処理
}

while文の条件式は、Bool型を返す必要があります。
条件式は実行文の実行前に毎回評価され、条件式の結果がtrueの場合は継続処理を行います。
結果がfalseの場合は繰り返しを終了し、while文全体の処理を停止します。

次のように条件式のa < 4が満たされる間は{ }内の処理を実行し続けます。
aの値が4になった時に条件式の結果はfalseを返すのでwhile文を終了します。

var a = 0

while a < 4 {
    print(a)
    a += 1
}

実行結果
0
1
2
3

repeat-while文

while文は、実行文の実行前に条件式の確認を行うため、
場合によっては一度も実行されないことがあります。

つまり、while文の繰り返し回数は0回以上ということになります。

条件式の成否によらず必ず1回以上の繰り返し処理を行いたい場合には、
repeat-while文を使用します。

repeat-while文はwhile文と違い、
実行文の実行後に条件式が評価されます。

repeat {
   1回は必ず実行されそれ以降は条件式が成立する限り実行される
} while 条件式

while文とrepeat-while文の違いを比較するために先ほどの例を少し変更し、
条件式の部分をa < 0に変更しました。

while文の場合は、条件式の結果がfalseなので一度も実行されることはなくwhile文が終了します。

var a = 0

while a < 0{
    print(a)
    a += 1
}

repeat-while文の場合は、初回実行が約束されているので0が出力されます。

repeat {
    print(a)
    a += 1
} while a < 0

実行結果

while文とrepeat-while文の使い分けについては理解できましたか?
結構単純な処理だと思いますので是非覚えてみてください!

以上、最後までご覧いただきありがとうございました。

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

App Store ConnectでApp Privacy Detailsを登録する

はじめに

  • 2020/12/8からアプリのプライバシー情報を申請時に登録が必要になりました。 今後はApp Storeにアプリのプライバシー情報がアプリページに表示されるようになります。 今回は登録時にやったことについてまとめました。
  • 詳細はこちら App privacy details on the App Store

項目

  • App Store Connect -> App -> App Store -> Appのプライバシーで登録できます スクリーンショット 2020-12-08 15.53.15.png

やったこと

  • でてくる質問に対して、該当するところにチェックを入れていきます。

スクリーンショット 2020-12-08 15.55.55.png

スクリーンショット 2020-12-08 15.56.25.png

  • 該当するデータタイプを選択していきます
  • ここまで終わると該当する項目が一覧で表示されます。

スクリーンショット 2020-12-08 16.04.24.png

  • ここからは各項目の情報の利用目的について、チェックしていきます。

スクリーンショット 2020-12-08 16.04.34.png

スクリーンショット 2020-12-08 16.05.59.png

  • 各項目はいつでも変更可能になっています。

スクリーンショット 2020-12-09 14.28.14.png

スクリーンショット 2020-12-09 14.41.13.png

  • 公開ボタンを押すとダイアログが表示され、公開を押すと反映されます

運用方法案

エクセルなどで管理する

  • 1番よく使われる方法だと思います。手動で項目を反映さえていくのが結構手間です。

fastlane

  • Uploading App Privacy Details
    • 設定項目をアップロードするActionが追加されている
    • JSONで管理できるので一度つくってしまえば楽だと思いました
upload_app_privacy_details_to_app_store(
  username: "your@email.com",
  team_name: "Your Team",
  app_identifier: "com.your.bundle",
  json_path: "fastlane/app_privacy_details.json"
)

[
  {
    "category": "PAYMENT_INFORMATION",
    "purposes": [
      "APP_FUNCTIONALITY"
    ],
    "data_protections": [
      "DATA_NOT_LINKED_TO_YOU"
    ]
  },
  {
    "category": "NAME",
    "purposes": [
      "PRODUCT_PERSONALIZATION",
      "APP_FUNCTIONALITY"
    ],
    "data_protections": [
      "DATA_LINKED_TO_YOU",
      "DATA_USED_TO_TRACK_YOU"
    ]
  }
]

まとめ

  • アプリのプライバシー情報を申請時に必要になりました。
  • fastlaneなどを使って、運用を楽にしていきたいと思いました。

参考リンク

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

swift基礎 UITextfieldsをLabelに表示する.

0.完成イメージ

https://twitter.com/kou09010901lam1/status/1334101860261662720?s=20

1. コード

DeleteButton()
 player1.removeAll()
    player2.removeAll()
    player3.removeAll()
    player4.removeAll()
    player5.removeAll()
    player6.removeAll()
    JL.text = "参加者"
    JL6.text = "参加者"
    JL2.text = "参加者"
    JL3.text = "参加者"
    JL4.text = "参加者"
    JL5.text = "参加者"
    NameCount = 1
JNNADDButton()
   @IBAction func JNADDButton(_ sender: UIButton) {
          if  NameCount == 1 {
    NameCount = NameCount + 1
            JL.text = JNtext.text
            player1 = JNtext.text!
          }
     else if  NameCount == 2 {
            NameCount = NameCount + 1
            JL2.text = JNtext.text
          player2 = JNtext.text!
          }
     else if  NameCount == 3 {
           NameCount = NameCount + 1
            JL3.text = JNtext.text
            player3 = JNtext.text!
          }
      else if  NameCount == 4 {
          NameCount = NameCount + 1
            JL4.text = JNtext.text
            player4 = JNtext.text!
          }
      else if  NameCount == 5 {
           NameCount = NameCount + 1
            JL5.text = JNtext.text
            player5 = JNtext.text!
          }
     else if  NameCount == 6 {
            NameCount = NameCount + 1
            JL6.text = JNtext.text
            player6 = JNtext.text!
          }
    JNtext.text = ""
        }

2.解説

1~6番までのラベルに表示するためには、Namecountで処理を6回分けることによって、実現しました。
.removeALLは削除のメソッドです
textFieldsはAnyで宣言します。actionで宣言すると、外からの処理がなぜかめんどくさかったのでやめました(何故だ???

3.関連記事

[Qlita]
[Qlita]
[Qlita]
[Qlita]

4.最後に

ラベルの文字数に制限があって文が省略される!とか細かいところは
attributes inspecter(右側のメニュー
をむちゃくちゃにいじると何故か改善し始める。などもあったので調べる前に全部いじってみる!を心がけました笑

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

[Swift]APIを使ってみる

表題通りです

APIを使ってみる!

地図をタップしてその位置の天気を取得する!

今回はOpenWeatherMapというAPIを使用しました。
APIキー取得は以下のサイトを参考にしております↓↓
https://auto-worker.com/blog/?p=1612

環境

Xcode 12.2
Swift 5.3.1

つくります

以前MapKitとCoreLocationを使うという記事で
地図をタップしてピンを立てる&タップ位置の住所を表示するというものを作ったので
そちらを活用して
タップされた位置の経度緯度情報をもとに天気情報を取得してみようと思います。

UIは適当です。
適当に天気表示用のUILabelと天気アイコン表示用のUIImageViewをおきます。
通信はAlamofire
JSON解析にはSwiftyJSONを使用しました。

import UIKit
import MapKit
import CoreLocation
import SwiftyJSON
import Alamofire

class TestViewController: UIViewController{


    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var weatherLabel: UILabel!
    @IBOutlet weak var weatherIcon: UIImageView!


    var latitude:CLLocationDegrees = 0.0
    var longitude:CLLocationDegrees  = 0.0
    let pin = MKPointAnnotation()

    override func viewDidLoad() {
        super.viewDidLoad()
        label.numberOfLines = 0
        label.text = "住所"
        weatherLabel.numberOfLines = 0

        //gestureを追加する
        let addGesture = UITapGestureRecognizer(target: self,action:#selector(didTap(_:)))
        mapView.addGestureRecognizer(addGesture)

        //pinを置く
        mapView.addAnnotation(pin)

    }


    @objc  func didTap(_ sender:UITapGestureRecognizer){
        // UIViewのタップ位置(CGPoint)を取得
        let tapPoint: CGPoint = sender.location(in: view)
        //CGPointから地図上の緯度経度に変換
        let locationCoordinate = mapView.convert(tapPoint, toCoordinateFrom: mapView)
        //タップした地図上の緯度経度を中心に地図を表示させる
        let region: MKCoordinateRegion = MKCoordinateRegion(center: locationCoordinate, latitudinalMeters: 5000, longitudinalMeters: 5000)
        mapView.setRegion(region, animated: true)

        //ピンの経度、緯度
        self.pin.coordinate = locationCoordinate

        //緯度経度→住所に変換(逆ジオコーディング)
        let latitude = locationCoordinate.latitude
        let longitude = locationCoordinate.longitude
        let location = CLLocation(latitude: latitude, longitude: longitude)

        let geocoder = CLGeocoder()
        //トレイリングクロージャ(関数の最後の引数がクロージャの場合()の外に{}書ける記法)
        geocoder.reverseGeocodeLocation(location) { placemarks,error  in
            guard let placemark = placemarks?.first,
                let postalCode = placemark.postalCode,
                let administrativeArea = placemark.administrativeArea,
                let locality = placemark.locality,
                let thoroughfare = placemark.thoroughfare,
                let subThoroughfare = placemark.subThoroughfare else {
                    return
                }
        //labelに住所表示
        self.label.text = "\(postalCode)\n\( administrativeArea)\( locality)\(thoroughfare)\(subThoroughfare)"

        //経度緯度から天気を取得してweatherLabel,weatherIconに設定   
        self.getWeather(lat: latitude, lon: longitude)

        }


    }


    func getWeather(lat:CLLocationDegrees,lon:CLLocationDegrees){

        let apiKey = "******(APIKey)*******"
        //lang:jaにするとdescriptionが日本語になる
        //unitsで温度表記を摂氏に指定。デフォルトだとケルビン
        let url = "http://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(apiKey)&lang=ja&units=metric"


        AF.request(url).responseJSON{ (response) in
            switch response.result {

            case .success:

                let json:JSON = JSON(response.data as Any)
                let weather = json["weather"][0]["main"].string
                let weatherIcon = json["weather"][0]["icon"].string
                let description =  json["weather"][0]["description"].string
                let temp =  json["main"]["temp"].int


                if let weather = weather,
                   let description = description,
                   let temp = temp,
                   let weatherIcon = weatherIcon {

                    self.weatherLabel.text = "\(weather)\n\(description)\n\(temp)℃"
                    self.weatherIcon.image = self.getIconByURL(iconID: weatherIcon)

                }


            case .failure:
              print("エラーです")

            }
        }
    }

    //URLから天気アイコンを取得
    func getIconByURL(iconID: String) -> UIImage{

        let url = "http://openweathermap.org/img/wn/\(iconID)@2x.png"

        let imageURL = URL(string: url)
        do {
            let data = try Data(contentsOf: imageURL!)
            return UIImage(data: data)!

        } catch {
            print("icon取得失敗")
        }

        return UIImage()
    }
}

シミュレーター立ち上げてみます

あれ?取得できてない:frowning2:

んん??
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

App Transport Security policyってなんですか。
ということで、別記事に対処法まとめました
[Swift]ATS設定
今回は↑こちらの記事の指定するドメインを許可する方法を試しました。

無事取得できました:relaxed:
スクショ

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

SVProgressHUDのimageにtintColorを反映させないようにする

はじめに

Qiita Advent Calendar 2020 Swift 9日目のエントリーです。

すみません、色々考えたんですが大したこと思いつかなかったので最近やったことについて。

SVProgressHUD

業務でローディングダイアログを実装する際にSVProgressHUDを利用しました。

特にアニメーションの指定などなく、基本的なものでよかったのでほぼ問題なかったんですが
そのまま利用するとtintColorがimageViewにもかかって、画像をそのまま利用しつつ文字色を別のものに変えるというのができませんでした。(※できるのかもしれないのですが、自分ではわかりませんでした。。)

なのでforkしてtintColorを画像に反映されないように修正して使用しました。

Screen Shot 2020-12-08 at 13.14.25.png

修正内容

僕の場合はそのあと画像に色を設定する必要がなかったので
「strongSelf.imageView.tintColor = strongSelf.foregroundImageColorForStyle;」
を適用させないようにif文をざっくり削除しました。
画像にもかけたいケースもある場合は分岐の条件を変更させたり必要だと思います。

SVProgressHUD.m
if (self.shouldTintImages) {
    if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
        strongSelf.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    }
    strongSelf.imageView.tintColor = strongSelf.foregroundImageColorForStyle;
} else {
    strongSelf.imageView.image = image;
}

↓

strongSelf.imageView.image = image;

実際に使う側での設定

hoge.swift
// ダイアログを表示するのに使用
private var requesting: Bool = false {
     didSet {
        if requesting {
            //  R.string.localizable.progress_loading()->"読み込み中"
            SVProgressHUD.show(withStatus: R.string.localizable.progress_loading())
        } else {
            SVProgressHUD.dismiss()
        }
    }
}

private func setupSVProgressHUD() {
    SVProgressHUD.setForegroundImageColor(.primary)
    SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 150))
    SVProgressHUD.setImageViewSize(CGSize(width: 60, height: 60))
    SVProgressHUD.setRingRadius(28)
    SVProgressHUD.setRingThickness(5)
    SVProgressHUD.setDefaultMaskType(.clear)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]App Transport Security policy

環境

Xcode 12.2
Swift 5.3.1 

エラー

Swiftでhttpリクエストの際にでたエラー

The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

原因

App Transport Security(ATS)という安全な通信を行うための機能(制限?)があり
ATSが有効な場合、http通信を行うことができないようです。(httpsはOK)
それでも、http通信がしたい!という場合の対処法を以下でまとめます。

対処法

:one: ATS無効化

Info.plist > Information Property List に
App Transport Security settings > Allow Arbitrary Loadsを追加しvalue を "Yes" に変更
info.plistスクショ
この方法だとATSが無効化されるため、すべてのhttp通信が可能となる。

:two:指定ドメインのみ許可

Info.plist > Information Property List > App Transport Security settings (なければ追加する)に

①Exception Domains を追加し、TypeをDictionaryにする

②Exception Domains 配下に
例外にしたいドメインを追加し、TypeをDictionaryにする

③例外にしたいドメイン配下に
 NSIncludesSubdomains   Type:Boolean Value:Yes
 NSTemporaryExceptionAllowsInsecureHTTPLoads  Type:Boolean Value:Yes
 NSTemporaryExceptionRequiresForwardSecrecy  Type:Boolean Value:No
info.plistスクショ

ドメインをさらに追加する場合は②、③を繰り返せばOK

以下のサイトに詳細があります
https://dev.classmethod.jp/articles/ios-ats-cheats-info-plist-settings/

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

[Swift]ATS設定

環境

Xcode 12.2
Swift 5.3.1 

エラー

Swiftでhttpリクエストの際にでたエラー

The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

原因

App Transport Security(ATS)という安全な通信を行うための機能(制限?)があり
ATSが有効な場合、http通信を行うことができないようです。(httpsはOK)
それでも、http通信がしたい!という場合の対処法を以下でまとめます。

対処法

:one: ATS無効化

Info.plist > Information Property List に
App Transport Security settings > Allow Arbitrary Loadsを追加しvalue を "Yes" に変更
info.plistスクショ
この方法だとATSが無効化されるため、すべてのhttp通信が可能となる。

:two:指定ドメインのみ許可

Info.plist > Information Property List > App Transport Security settings (なければ追加する)に

①Exception Domains を追加し、TypeをDictionaryにする

②Exception Domains 配下に
例外にしたいドメインを追加し、TypeをDictionaryにする

③例外にしたいドメイン配下に
 NSIncludesSubdomains   Type:Boolean Value:Yes
 NSTemporaryExceptionAllowsInsecureHTTPLoads  Type:Boolean Value:Yes
 NSTemporaryExceptionRequiresForwardSecrecy  Type:Boolean Value:No
info.plistスクショ

ドメインをさらに追加する場合は②、③を繰り返せばOK

以下のサイトに詳細があります
https://dev.classmethod.jp/articles/ios-ats-cheats-info-plist-settings/

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

クラシルとRxSwiftデビュー

こんにちは!

ずっと気になっていたサウナに先輩デザイナーに連れて行ってもらい完全にハマってしまい、最近は仕事中もサウナに行きたくて堪らない禁断症状が出ている
dely(クラシル)でiOSエンジニアをしています @RyogaBarbie です♨️

このnoteは dely Advent Calender #2 の8日目の記事となります。
dely Advent Calendar #1
dely Advent Calendar #2

昨日の記事はAndroidチームのマネージャーのうめもりさん(Twitter: @kr9ly) の記事になります!
Androidのビルド用Dockerイメージダイエット計画

クラシル

クラシルの凄腕PdMの奥原さんの紹介で今年9月にdelyのiOSエンジニアとして入社しました。
現在はレシピ動画サービスのクラシルの速度改善チームにてiOSを担当してます。

そんな奥原さんのAdvent Calendarは こちら

delyに入社した理由はたくさんあるのですが、いくつかあげると

  • 料理が好き(食べる)
  • 自粛期間で料理作ることが増えた
  • 働いてる人が良さそう(知り合いが働いてることで雰囲気もある程度つかめた)

といった感じです。

dely入社前にシェアされていたdelyの資料 これこれに記載されてるように情報がフルオープンの会社になってます。
slackを追えば何がどうなっているかがわかる会社です。
逆にslackのチャンネル多すぎ&流れるの早すぎで、最初は困惑しました!

そして、働いてる同僚が良い人しかいない、環境(オープンさ)がいいので心理的安全性高めで働くことができています!

ちょっとでも興味ある方は
Twitterまたは採用ページからぜひ!

RxSwift

現在クラシルではRxSwiftを使ったMVVMでの開発を行っています。
おおざっぱに書くと↓のような感じ
Router - View(ViewController) - ViewModel - UseCase - Repository
(細かい話はまたいつか…)

前職などではRxSwiftを使わないClosureを使ったMVVMでの開発だったので、RxSwiftを実務で使うのはクラシルが初めてでした。
一定まではClosureを使った開発でも特に辛さを感じないのですが、複数の非同期のAPIを待つなどで辛さが出てくるので zipには感動しました。
「細かいRxSwiftの書き方や使い方は実際に実務でいろいろ書かないとわからない」というのがRxSwiftを使ってみての感想です。

RxSwiftとMVVMの実装のStyle設計に関しては@yimajoRxSwift研究読本シリーズがオススメです。
そしてクラシルではUnioを導入してViewModelのStyleを揃えています。
Viewで発生するInputをViewModelに定義し、ViewControllerでViewModel.input.hoge(())のように発火させるのは導入当初は違和感しかなく、先輩エンジニアなど雑談したりしましたが、今では書き方統一するメリットの方が大きくUnio良きという気持ちです。

終わりに

明日は弊社CXOの坪田さん開発体制をSquad化してきてわかってきたコツと課題です!

そして、delyではエンジニア・デザイナー共に絶賛募集中です!
クラシルやTRILL開発部の情報が以下のリンク先にまとまっています。
採用ページ

また、開発メンバーが組織や技術・手法などについてざっくばらんにお話しするオンラインイベントを開催しています。「雰囲気をみてみたい」「聞きたいことがある」などありましたら、ぜひご参加ください!
オンラインイベント

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

[macOS][cocoa][Swift]なかなか調べるのに時間がかかったりしたのでメモ

忘れないようにメモ

個人的にアプリを作った際に調べたものをメモ.....φ(・∀・*)

スクロールビューの有効/無効を出来るようにする

class MyScrollView: NSScrollView {
    /// スクロール有効/無効
    var isScrollEnabled = true

    /// scrollWheel
    /// - Parameter event:
    override func scrollWheel(with event: NSEvent) {
        if self.isScrollEnabled {
            super.scrollWheel(with: event)
        }
    }
}

ESCをショートカットキーで利用する場合

他のアクションに繋いでも動かなくて悩みました。ESCキーはデフォルトで下記アクションが利用されるようで、overrideする事により出来るようになりました。

@IBAction override func cancelOperation(_ sender: Any?) {
}

presentAsSheet で呼び出したViewのサイズ固定

class MyViewController: NSViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // サイズを固定
        self.preferredContentSize = self.view.frame.size
    }
}

NSWindow のフルスクリーン判定

extension NSWindow {    
    // フルスクリーン判定
    var isFullScreen: Bool {
        return self.styleMask.contains(.fullScreen)
    }    
}

メニューの有効/無効

NSMenuItemValidationプロトコルの実装を使う。

extension ViewController: NSMenuItemValidation {
    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
        switch (menuItem.action) {
        // toggleSidebarメニューを条件によって有効/無効にする
        case #selector(NSSplitViewController.toggleSidebar(_:)):
            if self.hoge {
                return false
            } else {
                return true
            }
        default:
            return true
        }
    } 
}

他にも、あとで追記予定。。。

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

【Swift5】UITableViewCellで画像や色を変えたのにも関わらず同じセルが表示されるバグの対処法

はじめに

UItableViewCellのセルの再利用の仕組みによって、指定したcellの見た目を変える処理をした際に、過去のセルの変更が残ってしまうというちょっとしたバグが発生したので今回はUItableviewのセルの仕組みをみていきます

セルの再利用

TableViewは、大量にある同じ種類の情報をリスト表示する際に利用されます。そのため実装方法によってはセルの描画数が非常に多くなり、特にスクロール時には、パフォーマンスの低下が考えられます。これを解決するのがcellの再利用です。毎回新しくviewを作るのではなく以前に生成したcellを利用することで、メモリ割り当てを最小限にできます。
まずセルの再利用の際にはUItableviewにデフォルトで設定されているこのメソッドを使います

公式ドキュメント参照
https://developer.apple.com/documentation/uikit/uitableview/1614891-dequeuereusablecell

func dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell

そしてこのメソッドを、cellの生成されるタイミングで上のメソッドを発火させます

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//処理
}

このメソッドは、再利用可能なcellがあればそれを、なければ新しく作成したcellを返します。再利用可能なcellは、reuseIdentifierに紐づけられたreuse queueという場所に格納されています。

Reuse Queue

tableviewは裏側でreuse queueという、データの箱のようなものを持っており、reuseIdentifierごとにreuse queueが存在します。画面外に出たcellは、自身のidentifierに紐づいたreuse queueに追加されます。そして、同じidentifierのcellが表示されようとする時、queueから取り出されます。
下の図のようにスクロールしたら次のcellがqueueから取り出されるイメージ
dequeue.png

セルの再利用の落とし穴

   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // セルを取得(セルの再利用メソッド発火)
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellidendifier", for: indexPath)
        print(indexPath.row)
        //セルのviewを変更する処理
        cell.textLabel!.text = "\(self.data[indexPath.row])"
        if indexPath.row == 3{
            cell.backgroundColor = UIColor.red
        }

        return cell
    }

例えばこんな感じで指定したセルの色を変えたい時にこのような処理を書くと、背景色を赤に変えたセルは、他のセルにも再利用されてしまいます。
指定した以外の色は元に戻す必要があります

   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // セルを取得(セルの再利用メソッド発火)
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellidendifier", for: indexPath)
        print(indexPath.row)
        //セルのviewを変更する処理
        cell.textLabel!.text = "\(self.data[indexPath.row])"
        if indexPath.row == 3{
            cell.backgroundColor = UIColor.red
        } else {
            cell.backgroundColor = 元の色               
        }

        return cell
    }

カスタムセルの中身の画像を動的に変更したい場合はこのようにカスタムセルの画像を初期化する必要があります

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // セルを取得(セルの再利用メソッド発火)
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellidendifier", for: indexPath)
        //画像を初期化
        cell.sampleimage.image = nil
        return cell
    }

最後に

セルを再利用してることで前の状態が残るので、想定とは異なるview状態になります
今後似たような症状が起きた場合は、再利用によるセルリセットし忘れを疑いましょう

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

Xcode12以降のPlayground上でSwiftのパッケージをインポートする方法

はじめに

Xcode12以降のPlaygroundにおいて、Swiftのパッケージ?をドラッグ・アンド・ドロップすることで簡単にインポートしてできる機能が追加されました。
今までは簡単にドラッグ・アンド・ドロップでインポートできなかったのでこの使い方を紹介させて頂きたいと思います。

https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes
スクリーンショット 2020-12-06 19.41.55.png

Playgroundとは

プレイグラウンド(Playground)は別のプロジェクトの生成をしなく、Swiftのコーディング結果をリアルタイムで確認できる機能です。
基本的な文法から複雑なコードまで様々なケースで利用することができます。Swiftを試しに動かしたり、文法の練習にも使えるいいツールです。

使い方

まず、新しいプロジェクトを作成します。
Xcodeを開いてFile > New > Playgroundを選択してPlaygroundのファイルを該当プロジェクトに追加します。
スクリーンショット 2020-12-06 19.49.31.png

その後、Playgoundのファイルを開いた状態で
File > Save As Workspaceを選択してworkspaceファイルを生成します。

スクリーンショット 2020-12-06 19.53.58.png

?注意点として、ファイル生成後、右側のshow the file inspectorからRelative to Groupのオプションを選択することが必要です。
またPlayground SettingsのPlatformを現在使用しているPlatformを設定してBuild Active Schemeにチェック☑️を入れます。

スクリーンショット 2020-12-06 20.03.18.png

ここまでできたらworksapceファイルを保存して再度開きます。
インポートしたいSwiftのバッケージをダウンロードしてXcodeの左側のファイルリストがある場所にドラッグアンドドロップします。

インポートが終わったらすぐにPlaygroundに使用することができます。

以下はPlotというパッケージをインポートしてみた列です。

スクリーンショット 2020-12-06 20.12.02.png

スクリーンショット 2020-12-06 19.07.10.png

まとめ

PlaygroundはまだSwiftに慣れてない人や練習コードを作成したい人たちにとっても便利な機能だと思います。
これを利用して楽しくコーディングをしましょう!

参考:
Plot
Importing Swift packages into a playground in Xcode 12

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

繰り返し文について〜for-in文〜

繰り返し文の種類

繰り返し処理を行う代表的な制御構文には、for-in文while文が存在します。

for-in文はシーケンスの要素数によって、
while文は継続条件の評価によって繰り返しの終了を決定します。

また、repeat-while文という制御構文も存在します。
repeat-while文は実行文の初回実行を保証する制御構文になります。

for-in文

for-in文は、Sequenceプロトコルに準拠している型の要素にアクセスするための制御構文です。

Sequenceプロトコルに準拠している代表的な型には、
Array<Element>型Dictionary<Key, Value>型などがあります。

for-in文は次のように記述します。
要素名は基本的になんでも構いませんが、
意味のある分かりやすい要素名にした方が分かりやすくなります。

for 要素名 in シーケンス {
   要素ごとに繰り返し実行される処理
}

Array<Element>型

Array<Element>型の要素をfor-in文で列挙する場合、要素の型はElement型になります。
つまり、Array<Int>型の要素をfor-in文で取り出す場合の要素の型はInt型になります。

let arrayInt = [1, 2, 3, 4, 5]
var count = 0

for int in arrayInt {
    count += int
}

print(count)

実行結果
15

arrayIntの中に格納されている要素を先頭から順に取り出して、
for int in arrayIntのintの中に代入しています。

for-in文の中の処理を全て終えるとarrayIntの次の要素にアクセスしintに入れる。
それをarrayIntの先頭から末尾まで繰り返していきます。

Dictionary<Key, Value>型

Dictionary<Key, Value>型の要素をfor-in文で列挙する場合、
要素の型は、(Key, value)型のタプルとなります。

例えば、Dictionary<String, Int>型の値をfor-in文に渡すと、
要素は(String, Int)型になります。

なお、Dictionary<Key, Value>型は要素の順序を保証していないため、
環境によっては実行結果の順序が異なるかもしれません。

let dictionary = ["a": 1, "b": 2]

for (key, value) in dictionary {
    print("Key: \(key), Value: \(value)")
}

実行結果
Key: a, Value: 1
Key: b, Value: 2

Range<Bound>型

また、範囲型についてもfor-in文で回すことが可能です。

今回は、1から10までの値を順にitemに代入しそれらを足す処理を行っています。

var count = 0

for item in 1...10 {
    count += item
}

print(count)

実行結果
55

これらが代表的なfor-in文になります。

個人的にはif文より使用頻度は低いと思いますが、
かなり重要な制御構文なので絶対に覚えた方が良いと思います。

以上、最後までご覧いただきありがとうございました。

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