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

macOSで隠しファイルを編集する!

mac:pythonコマンドでpython3を利用する

ホームディレクトリの中ある".bash_profil"にコードを書いて使えるようにします。このファイルは普段表示されていません。そこで表示させましょう!

"⌘(コマンド)"と"シフト"と ".(ドット)"を表示したいフォルダを開いて、有効になっている時に同時に押してみましょう。

".(ドット)"から始まるファイルが表示されるはずです。こうなれば普通にファイル編集が可能になるので、".bash_profil"などもCotEditerなど通常使っているものを使えます。

今回はmacにデフォルトで入っているPythonは2.7ですがエイリアスを指定してPython 3.7を"python"のコマンドで起動するようにします。

.bash_profilにalias python=python3 を記述します。これだけです。

ターミナルで"python"と打つと、python3.7が起動しました!

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

ラベルの一部分の色を変える(Swift)

ラベルの色を部分的に変えたい場合、少し調査が必要だったのでまとめます。
主に2パターンの方法があります。

その1 StroryBoardを使う

この方法だと、コードを書かなくていいので、設定が簡単にできます

スクリーンショット 2019-05-06 2.17.44.png

対象のラベルを選択した状態にします。

①TextをAttributedにします
②ラベルに表示する文言の色を変えたい部分だけ選択した状態で色を変える(赤枠の部分です)

これだけで部分的に色を変えれます。

その2 ソースコードで定義する

この方法だとStoryboardの設定は不要ですが、実装が必要です。

まずは、ラベルとソースコードでの定義を紐付けます

スクリーンショット 2019-05-06 2.27.54.png

上記のように黒い丸が付いていれば紐付けされているはずです。

次に、ラベルの設定をします。

controller.swift
let attrText1 = NSMutableAttributedString(string: taskLimitLabel.text!)

        attrText1.addAttributes([
            .foregroundColor: UIColor.red,
            ], range: NSMakeRange(6, 9))

taskLimitLabel.attributedText = attrText1

上の処理では属性を加える文字列を定義して、addAttributesで属性を加えています。
今回はrangeで色を変える文字が何番目かを設定し、foregroundColorで色を設定します。
最後に、属性を追加した文字をラベルに置き換えます。
これで一部分の色が変わります。

今回は2パターン記載しました。
実装でカスタマイズする方法は他にもあると思います。
個人的にはStoryboardを使ったほうが視覚的に見やすいと思いますが、複数人で開発など、ソースで書いた方がいいパターンもあると思うので、場合によって使い分けたいと思います。

参照

Apple Developer (NSAttributedString)

Apple Developer (NSMutableAttributedString)

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

iOS / macOS どちらでも使用できる UIKit/AppKit クラスの定義を考える

はじめに

iOS の UIKit と macOS の AppKit はクラス名が前者は UI プレフィックス、後者が NS プレフィックスである。
例えば、UIColor / NSColor, UIButton / NSButton などがある。

iOS / macOS どちらでも動作するコードを書く際、いちいちコード内でプラットフォームごとにクラス名を切り分けすることは面倒なので、この違いを吸収できるような定義の方法を考える。

定義例

以下のような定義を入れておくと、 CrossPlatformXXX でプラットフォームを意識せずにクラス名を書ける。

CrossPlatform.swift
#if os(iOS)
import UIKit

public typealias CrossPlatformRect = CGRect
public typealias CrossPlatformColor = UIColor
public typealias CrossPlatformViewController = UIViewController
#elseif os(macOS)
import AppKit

public typealias CrossPlatformRect = NSRect
public typealias CrossPlatformColor = NSColor
public typealias CrossPlatformViewController = NSViewController
#endif

UIKit / AppKit のクラスで共通のインターフェースを使う分にはこれで OK なのだが、クラスのインターフェースが違う部分を含めて同一クラスのように扱いたい場合は、インターフェースをどちらかにすり合わせると良い。

例えば、 UIButton(UIControl) には addTarget メソッドが存在するが、 NSButton(NSControl) には addTarget メソッドは存在しない。

この場合、NSButton のインターフェースを UIButton に寄せて同一のように扱うためには、以下のようにする。

CrossPlatformButton.swift
#if os(iOS)
import UIKit

open class CrossPlatformButton: UIButton {
  open func addTarget(_ target: Any?, action: Selector) {
    addTarget(target, action: action, for: .touchUpInside)
  }
}

#elseif os(macOS)
import AppKit

open class CrossPlatformButton: NSButton {
  open func addTarget(_ target: Any?, action: Selector) {
    self.target = target as AnyObject?
    self.action = action
  }
}

#endif

これで CrossPlatformButton の addTarget メソッドを使用することで、ターゲットとアクションの設定を、プラットフォームの差を意識せず実施できるようになる。

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

アクション付き通知を作ってみる

はじめに

通知に対するアクションを実装したくて書きました.
LI◯EやMESSE◯GERの通知を3Dタッチした時に出てくるものを想像していただければ大体合ってます.基本的にはローカル通知の実装と一緒です.

通知許可のリクエスト

まずはUserNotificationsのimport

AppDelegate.swift
import UserNotifications

iOS9, iOS10以降で分けてリクエスト
通知許可ダイアログの表示

AppDelegate.swift
func application(_ application: UIApplication, 
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

if #available(iOS 10.0, *) {
            // iOS 10
            let center = UNUserNotificationCenter.current()
            // optionsでバッジ,サウンド,アラートを設定
            // アラートだけにすれば上から降りてくるやつだけになる
            center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { (granted, error) in
                if error != nil {
                    return
                }

                if granted {
                    debugPrint("通知許可")
                } else {
                    debugPrint("通知拒否")
                }
            })

        } else {
            // iOS 9
            let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)
            UIApplication.shared.registerUserNotificationSettings(settings)
        }

}

ここまではローカル通知と一緒です.

次に具体的なアクションの設定と,通知からアクションを選択した際の動作を書き込みます.
今回はアクションとラベルを2つ用意して,対応したアクションでカウントアップさせます.

ViewController
import UIKit
import UserNotifications

// アクションをenumで宣言
enum ActionIdentifier: String {
    case actionOne
    case actionTwo
}

// Delegateの宣言を忘れずにする
class ViewController: UIViewController, UNUserNotificationCenterDelegate {

    var one: Int = 0
    var two: Int = 0

    @IBOutlet var label1: UILabel!
    @IBOutlet var label2: UILabel!

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

        // アクション設定
        let actionOne = UNNotificationAction(identifier: ActionIdentifier.actionOne.rawValue,
                                            title: "アクション1",
                                            options: [.foreground])
        let actionTwo = UNNotificationAction(identifier: ActionIdentifier.actionTwo.rawValue,
                                            title: "アクション2",
                                            options: [.foreground])

        let category = UNNotificationCategory(identifier: "category_select",
                                              actions: [actionOne, actionTwo],
                                              intentIdentifiers: [],
                                              options: [])

        UNUserNotificationCenter.current().setNotificationCategories([category])
        UNUserNotificationCenter.current().delegate = self


        let content = UNMutableNotificationContent()
        content.title = "こんにちわ!"
        content.body = "アクションを選択してください!"
        content.sound = UNNotificationSound.default

        // categoryIdentifierを設定
        content.categoryIdentifier = "category_select"

        // 60秒ごとに繰り返し通知
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
        let request = UNNotificationRequest(identifier: "notification",
                                            content: content,
                                            trigger: trigger)

        // 通知登録
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

    }

    // アクションを選択した際に呼び出されるメソッド
    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: () -> Swift.Void) {

        // 選択されたアクションごとに処理を分岐
        switch response.actionIdentifier {

        case ActionIdentifier.actionOne.rawValue:
            // 具体的な処理をここに記入
            // 変数oneをカウントアップしてラベルに表示
            one = one + 1
            label1.text = String(one)

        case ActionIdentifier.actionTwo.rawValue:
            // 具体的な処理をここに記入
            two = two + 1
            label2.text = String(two)

        default:
            ()
        }

        completionHandler()
    }


}

完成図

qiita.gif

参考記事

iOS 10 User Notifications Framework実装まとめ

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

【Swift】KeyPath 式が生成する実際のオブジェクトについて

Swift4 で導入された KeyPath は、init ではなく、次のような \<Type>.<path> の形式でオブジェクトを生成します。

struct Foo {
    var bar: Int
}

let keyPath = \Foo.bar // WritableKeyPath<Foo, Int>

上記の場合、 WritableKeyPath に型推論されて、次のように読み書き可能です。

var foo = Foo(bar: 0)
print(foo[keyPath: keyPath]) // 0
foo[keyPath: keyPath] = 1
print(foo[keyPath: keyPath]) // 1

また、var ではなく、let で宣言した場合は、KeyPath クラスに型推論されます。

struct Foo {
    let bar: Int
}

let keyPath = \Foo.bar // KeyPath<Foo, Int>

このように、プロパティの宣言方法に応じて、下記のいずれかの型に型推論をするようです。

  • KeyPath
  • WritableKeyPath
  • ReferenceWritableKeyPath

どのように宣言すると、それぞれの型に推論されるのか?気になったので Playground で動作確認をしてみました。

環境

  • Xcode Version 10.2.1
  • Apple Swift version 5.0.1

KeyPath

読み込み専用のクラスである KeyPath<Root, Value> は、プロパティの宣言を次のようにした場合に推論されます。

  • let で宣言した定数
  • getter のみの計算プロパティ
  • { get } 宣言したプロトコルのプロパティ

let で宣言した定数

struct Foo {
    let bar: Int = 0
}

getter のみの計算プロパティ

struct Foo {
    var bar: Int {
        return 0
    }
}

get のみ宣言したプロトコルのプロパティ

protocol Foo {
    var bar: Int { get }
}

このような場合、明示的に WritableKeyPath 型に代入しようとしてもコンパイルエラーになります。

let keyPath: WritableKeyPath<Foo, Int> = \Foo.bar // コンパイルエラー

また、 WritableKeyPath へのダイナミックキャストは実行時エラーになります。

let keyPath = \Foo.bar as! WritableKeyPath<Foo, Int> // 実行時エラー

実際に、次のようにして実行時の型を調べると KeyPath 型であることが分かります

let keyPath = \Foo.bar
type(of: keyPath) // KeyPath<Foo, Int>

WritableKeyPath

読み書き両用のクラスである WritableKeyPath<Root, Value> は、プロパティの宣言を次のようにした場合に推論されます。

  • var で宣言した変数
  • getter / setter 計算プロパティ
  • { get set } 宣言したプロトコルのプロパティ

var で宣言した変数

struct Foo {
    var bar: Int = 0
}

getter / setter 計算プロパティ

struct Foo {
    var bar: Int {
        get { return 0 }
        set {}
    }
}

get set 宣言したプロトコルのプロパティ

protocol Foo {
    var bar: Int { get set }
}

このような場合、明示的に WritableKeyPath 型に代入することもできます。

let keyPath: WritableKeyPath<Foo, Int> = \Foo.bar // OK!

また、 WritableKeyPath のスーパークラスにキャストすることもできます。
次のように KeyPath として宣言することも可能です。

let keyPath: KeyPath<Foo, Int> = \Foo.bar // OK!

実行時の型を調べましたところ、型宣言の有無によらず WritableKeyPath でした。
次のように KeyPath として宣言しても、実際には WritableKeyPath 型のオブジェクトなので、
ダイナミックキャストも問題なくできます。

let keyPath: KeyPath<Foo, Int> = \Foo.bar
type(of: keyPath) // WritableKeyPath<Foo, Int>
keyPath as! WritableKeyPath<Foo, Int> // OK!

ReferenceWritableKeyPath

構造体などの値型で読み書き両用プロパティを宣言した場合は WritableKeyPath ですが、クラスで宣言した場合は ReferenceWritableKeyPath になります。

var で宣言した変数

class Foo {
    var bar: Int = 0
}

getter / setter 計算プロパティ

class Foo {
    var bar: Int {
        get { return 0 }
        set {}
    }
}

get set 宣言したプロトコルのプロパティ

protocol Foo: AnyObject {
    var bar: Int { get set }
}

プロトコルは AnyObject などでクラスに制限することで ReferenceWritableKeyPath になります。
WritableKeyPath の場合と同様に、型宣言した場合も、実際のオブジェクトは ReferenceWritableKeyPath になります。

let keyPath: KeyPath = \Foo.bar
type(of: keyPath) // ReferenceWritableKeyPath

ここまでのまとめ

プロパティの宣言方法に応じて、下記のいずれかの型に型推論されます。

  • KeyPath
  • WritableKeyPath
  • ReferenceWritableKeyPath

KeyPath に推論される場合は、実行時のオブジェクトも KeyPath で、WritableKeyPath など書き込み可能なキーパスで型宣言しようとするとコンパイルエラーになります。

WritableKeyPath に推論される場合は、実行時のオブジェクトも WritableKeyPath で、KeyPath にアップキャスト可能で、またその後 WritableKeyPath にダウンキャストすることも可能です。

クラスの場合は、読み書き可能なプロパティは ReferenceWritableKeyPath になります。

アクセス制御をした場合

Swift では、セッターのアクセスを制御して内部のみ公開することができます。

struct Foo {
    private(set) var bar: Int = 0 // getter は internal, setter は private
}

このような場合は、キーパスオブジェクトの生成場所によって、
型推論の挙動が変わりました。

  • setter にアクセスできる場合
  • getter のみアクセスできる場合

setter にアクセスできる場合
setter にアクセスできる場合は、(Reference)WritableKeyPath に型推論されます。

struct Foo {
    private(set) var bar: Int = 0

    func foo() {
        let keyPath = \Foo.bar // WritableKeyPath<Foo, Int>
    }
}

getter のみアクセスできる場合
getter のみアクセスできる場合は、KeyPath に型推論されます。

struct Foo {
    internal private(set) var bar: Int = 0
}

class FooUser {
    func use() {
        let keyPath = \Foo.bar // KeyPath<Foo, Int>
    }
}

明示的に WritableKeyPath 型を宣言してもコンパイルエラーになります。

class FooUser {
    func use() {
        let keyPath: WritableKeyPath<Foo, Int> = \Foo.bar // コンパイルエラー
    }
}

なのですが、実行時のオブジェクトは WritableKeyPath なので、ダイナミックキャストをしても実行時エラーになりません。

アクセス制御を無視して、次のように書き込みすることができます。

class FooUser {
    func use() {
        let keyPath = \Foo.bar as! WritableKeyPath<Foo, Int>
        var foo = Foo()
        print(foo.bar) // 0
        // foo.bar = 2 // コンパイルエラー
        foo[keyPath: keyPath] = 2 // WritableKeyPath 経由で書き込みができる
        print(foo.bar) // 2
    }
}

このようにアクセス制御を無視したコードを実際のプロダクトで書くことはないとは思いますが、内部で書き込みが可能なプロパティは、アクセスレベルによらず実行時のオブジェクトは WritableKeyPath になることが分かりました。

コンパイル時の検査は、アクセスする箇所に応じた KeyPath として推論してくれるので、
この仕組を利用することで Swift らしいコードが記述できそうです。

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

Google Cloud Run を Swift 5 + IBM Kitura で動かす

Google Cloud Run は基本的にどんな言語でも動かすことが出来ます。
Cloud Run の Quickstart にはコミュニティサンプルとして Swift 4.2 と Swifty を使ったサンプルが掲載されています。

この記事では Cloud Run を Swift 5 と IBM の Kitura で動かしてみます。
私のブログ記事のほうではスクリーンショット付きで詳しく解説していますので、そちらも合わせてご覧いただければと思います。

あえて始めに雑感を書く

コンテナ等使ったことがなかった私でも簡単にデプロイすることが出来ました。
main.swift のほうですが、コミュニティサンプルの Swifty のほうはポートの取得や Kitura でいう Kitura.run() の記述に一工夫されているのが見られるので、その点は改善点なのかなとざっくり思います。
Cloud Functions for Firebase で「うわぁ…どうしても JavaScript (TypeScript) で書かなきゃダメか……」と思っていたところで突然降ってきた Cloud Run。私の大好きな Swift で書けるのはとっても嬉しいですし、モチベが全然違います…。

Google Cloud Run を Swift 5 + IBM Kitura で動かす - notes from E | 広く薄くの雑記帳 by treastrain

環境

  • Xcode Version 10.2.1 (10E1001)
  • Apple Swift version 5.0.1 (swiftlang-1001.0.82.4 clang-1001.0.46.5) Target: x86_64-apple-darwin18.5.0
  • Kitura version 2.7.0

始める前に

GCP のプロジェクトの課金が有効になっているかどうか確認します。
私の場合、将来的に Firebase Hosting に繋ぎたいので、Firebase プロジェクトの課金を有効にします。

プロジェクトの課金を有効にしたあとは Cloud Run API を有効にします。

続けて Google Cloud SDK の components を最新にアップデートします。

$ sudo gcloud components update

Swift 5 でサンプルアプリケーションを作成する

Package.swift の準備

作業するためのディレクトリを作成し、そこで $ swift package init --type=executable します。

Package.swift は次のようにし、IBM Kitura を持ってきます。

Package.swift
// swift-tools-version:5.0

import PackageDescription

let package = Package(
    name: "cloudrun",
    dependencies: [
        .package(url: "https://github.com/IBM-Swift/Kitura.git", .upToNextMinor(from: "2.7.0")),
    ],
    targets: [
        .target(
            name: "cloudrun",
            dependencies: [
                "Kitura",
            ]),
        .testTarget(
            name: "cloudrunTests",
            dependencies: ["cloudrun", "Kitura"]),
    ]
)

Package.swift を書いたら $ swift build します。
成功したら、以降は Xcode の補完を効かせたいので、 $ swift package generate-xcodeproj で Xcode プロジェクトを作成することにします。

main.swift を書く

作成した Xcode プロジェクトを開いて、main.swift を次のようにします。

main.swift
import Foundation
import Kitura


let router = Router()

router.get("/") { request, response, next in
    response.send("Hello world")
    next()
}

Kitura.addHTTPServer(onPort: 8080, with: router)
Kitura.run()

Xcode での Build が成功すれば OK です。もし Run すると localhost の 8080 番で動作確認することもできます。

Dockerfile を作成

続いて Dockerfile を作成します。

# Use the official Swift image.
# https://hub.docker.com/_/swift
FROM swift:5.0.1

# Copy local code to the container image.
WORKDIR /app
COPY . .

# Install dependencies and build.
RUN apt-get install openssl libssl-dev libcurl4-openssl-dev
RUN swift build -c release

# Run the web service on container startup.
CMD [ ".build/release/cloudrun"]

コンテナ化して Container Registry にアップロードする

作業用ディレクトリで次のコマンドを実行します。
[PROJECT-ID] は Google Cloud Platform の プロジェクトIDです。

$ gcloud config set project [PROJECT-ID]
$ gcloud builds submit --tag gcr.io/[PROJECT-ID]/helloworld

Cloud Run にデプロイする

ここまで来たらあとは Cloud Run にデプロイするだけです。

$ sudo gcloud components install beta
$ gcloud components update
$ gcloud beta run deploy --image gcr.io/[PROJECT-ID]/helloworld

リージョンの選択のあとに Service name: (helloworld): と聞かれますが、この場合はここに何も入力しなくて大丈夫でした。

出力された URL にアクセスすると Hello world と返ってきます。

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

Swift で paiza を攻略するための予備知識

paizaのスキルチェックをはじめました。
スキルチェックは、いわゆる競技プログラミングの成績で就職活動をするためのサービスです。

とりあえずこれだけ知っていればSwiftでpaizaのスキルチェックがはじめられるだろうというものを紹介します。

paizaのSwiftのバージョンについて

以下のURLに各言語のバージョンが記載されていますが、現時点(2019/05/06)ではswiftは3.0.1です。
ちょっと古いので最新のXCodeのPlaygroundなどでコードを試すならコピペする際エラーが出るかもしれませんね。

各言語のバージョン、環境情報

同じくpaizaのサービスであるpaiza.ioならswift4.2で実行出来ます。
swift3.0.1でプログラミング出来る環境が簡単に手に入れば紹介したいのですが、とりあえず私はpaiza.ioで満足しています。

標準入出力

まずは標準入出力から紹介します。
以下はpaizaのスキルチェックでSwiftを選択すると最初から与えてもらえるコードです。

let input_line=readLine()! // 標準入力から1行目を取得
print(input_line)

「readLine()」は標準入力を1行ずつ読み込んでくれる関数です。2行読ませたい場合は以下のように書きます。

let input_line1 = readLine()! // 標準入力から1行目を取得
let input_line2 = readLine()! // 標準入力から2行目を取得

よく使う文字列操作

標準入力から与えられる文字列から複数の値を配列で受け取る方法です。

let input_line = "1 3 5 7"
let strings = input_line.split(separator: " ")
print(strings) // => ["1", "3", "5", "7"]

map, filter, reduce の書き方

関数型プログラミングに欠かせない関数もの使い方も紹介します。これらの関数はいろいろな書き方が出来るようですが、競技プログラミングで使いそうな書き方だけ紹介します。

mapの書き方

例1:文字列→数値に変換

let input_line = "1 3 5 7"
let strings = input_line.split(separator: " ")
let numbers = strings.map({Int($0)!}) // $0から配列の一つ一つの要素にアクセス出来ます。
print(numbers) // => [1, 3, 5, 7]

例2:数値を2倍

let input_line = "1 3 5 7"
let strings = input_line.split(separator: " ")
let numbers = strings.map({Int($0)! * 2})
print(numbers) // => [2, 6, 10, 14]

filterの書き方

例:3の倍数のみに絞り込む

let input_line = "1 2 3 4 5 6"
let strings = input_line.split(separator: " ")
let numbers = strings.map({Int($0)!})
let filteredNumbers = numbers.filter({ $0 % 3 == 0 })
print(filteredNumbers) // => [3, 6]

reduceの書き方

第2引数に演算子を渡せるあたりがswiftらしいですね。

例:総和を求める

let input_line = "1 2 3 4 5 6"
let strings = input_line.split(separator: " ")
let numbers = strings.map({Int($0)!})
let number = numbers.reduce(0, +)
print(number) // => 21

mapやfilterでインデックスを受け取る

javascriptでmapやfilterを使う場合、配列の何番目か(インデックス)を第2引数で受け取れます。

const newArray = ["あ", "い", "う", "え", "お"]
                 .map((char, index) => `${index}: ${char}`);
console.log(newArray) // => 0: あ, 1: い, 2: う, 3: え, 4: お

swiftでインデックスを取得するには「.enumerated()」を使います。またmapやfilter、enumeratedは配列を返す関数なので、メソッドチェーンで繋いで書くことが出来ます。
\$0.0からインデックス、$0.1で配列の要素そのものにアクセス出来ます。

mapでインデックスを使う例

文字列にインデックス情報を付け加える例

let input_line = "あ い う え お"
let strings = input_line.split(separator: " ")
let mappedStrings = strings
  .enumerated()
  .map({ String($0.0) + ": " + $0.1 })

print(mappedStrings) // => ["0: あ", "1: い", "2: う", "3: え", "4: お"]

filterでインデックスを使う例

配列から最初と最後の要素を除外する例

let input_line = "あ い う え お"
let strings = input_line.split(separator: " ")
let filteredStrings = strings
  .enumerated()
  .filter({ $0.0 != 0 && $0.0 != strings.count - 1 })
  .map({$0.1})
print(filteredStrings) // => ["い", "う", "え"]

任意の配列を生成する

mapのもう一つの書き方

任意の配列を生成する方法を紹介する前に、mapのもう一つの書き方を紹介しておきます。
以下の2つのコードは同じ結果になります。

let input_line = "1 3 5 7"
let strings = input_line.split(separator: " ")

// $0から配列の一つ一つの要素にアクセス
let numbers1 = strings.map({Int($0)!})
print(numbers1) // => [1, 3, 5, 7]

// 変数(char)を宣言して、その変数で一つ一つの要素にアクセス
let numbers2 = strings.map({char in Int(char)!}) 
print(numbers2) // => [1, 3, 5, 7]

変数を任意に宣言することでmap関数を入れ子で使用出来ます。

一次元配列の生成

配列を生成した後、map関数であれこれして問題を解くために必要なデータを作成します。

let input_line = "3"
let number = Int(input_line)!
let array = (0...(number - 1)).map({$0})
print(array) // => [0, 1, 2]

二次元配列の生成

以下のコードはタプルにx,yの値を持たせた二次元配列を生成しています。

let input_line = "2 3"
let input_numbers = input_line.split(separator: " ").map({Int($0)!})
let height = input_numbers[0]
let width  = input_numbers[1]
let table = (0...(height - 1))
    .map({y in
        (0...(width - 1)).map({x in (x: x, y: y)});
    })

print(table)
/**
 * [ 
 *     [(x: 0, y: 0), (x: 1, y: 0), (x: 2, y: 0)],
 *     [(x: 0, y: 1), (x: 1, y: 1), (x: 2, y: 1)]
 * ]
 */

まとめ

以上です。競技プログラミングは文法が分かってやっとスタートライン。
あとはアルゴリズム力を存分に発揮して問題を解いて下さい。

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

AVSpeechSynthesizerでの初回音声読み上げが遅いのを直す

iOSで音声読み上げをする時のコード。

import AVFoundation

class SpeechViewController: UIViewController {
    private let synthesizer = AVSpeechSynthesizer()

    private func speech(_ text: String) {
        // 初回のみ0.1~2秒遅れて読み上げられる
        let utterance = AVSpeechUtterance(string: text)
        synthesizer.speak(utterance)
    }

ただこれだと初回の読み上げのみ0.1~2秒遅れて読み上げられる。
2回目以降は遅延なく読み上げられる。
なのでviewDidLoadで空文字を1回読んでおく。

import AVFoundation

class SpeechViewController: UIViewController {
    private let synthesizer = AVSpeechSynthesizer()

    override func viewDidLoad() {
        super.viewDidLoad()

        // 空文字を読んでおく
        let utterance = AVSpeechUtterance(string: "")
        utterance.volume = 0
        synthesizer.speak(utterance)
    }

    private func speech(_ text: String) {
        // 遅延なく読み上げられる
        let utterance = AVSpeechUtterance(string: text)
        synthesizer.speak(utterance)
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

使いやすいUIAlertControllerのExtensionを考えたので載せてみる

実装

extension UIAlertController {
    static func noButtonAlert(title: String?, message: String?) -> UIAlertController {
        return UIAlertController(title: title, message: message, preferredStyle: .alert)
    }
    static func okAlert(title: String?,
                        message: String?,
                        okHandler: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(.init(title: "OK", style: .default, handler: okHandler))
        return alert
    }

    static func errorAlert(title: String? = "⚠️",
                           error: Error,
                           okHandler: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
        let alert = UIAlertController(title: title, message: "\(error)", preferredStyle: .alert)
        alert.addAction(.init(title: "OK", style: .default, handler: okHandler))
        return alert
    }

    static func fieldAlert(title: String?,
                           message: String?,
                           placeholder: String?,
                           handler: ((String?) -> Void)? = nil) -> UIAlertController {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addTextField {
            $0.placeholder = placeholder
        }
        alert.addAction(.init(title: "OK", style: .default, handler: { (action: UIAlertAction) in
            handler?(alert.textFields?.first?.text)
        }))
        alert.addAction(.init(title: "Cancel", style: .cancel, handler: { (action) in
            handler?(nil)
        }))
        return alert
    }
}

extension UIViewController {
    func present(_ alert: UIAlertController, completion: (() -> Void)? = nil) {
        present(alert, animated: true, completion: completion)
    }

    func present(_ alert: UIAlertController, _ autoDismissInterval: TimeInterval, completion: (() -> Void)? = nil) {
        present(alert, animated: true, completion: { [weak self] in
            DispatchQueue.main.asyncAfter(deadline: .now() + autoDismissInterval) {
                self?.dismiss(animated: true, completion: completion)
            }
        })
    }
}

使い方

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // OKボタンのみのAlertを表示
        present(.okAlert(title: nil, message: "グループを作成しました"))

        // OKボタンのついたErrorを表示するAlertを表示
        present(.errorAlert(error: NSError(domain: "hoge", code: 0, userInfo: nil)) { _ in
        // ユーザが閉じたあとに行う処理を記述
        })

        // UITextFieldつきのAlertを表示
        present(.fieldAlert(
            title: "グループの作成", message: "グループ名を入力してください", placeholder: "グループ名",
            handler: { [weak self] (inputText) in
                // 入力されたテキストを使う処理を記述
        }))

        // 0.3秒後に自動で閉じる
        present(.noButtonAlert(title: "✅", message: "保存しました"), 0.3) { [weak self] in
            // ユーザが閉じたあとに行う処理を記述
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【学習記録16】2019/5/6(月)

学習時間

5.0H

使用教材

改訂新版Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus

学習分野

第2章変数、定数と基本的な型
第3章制御構文

コメント

学習開始からの期間:16日目
今日までの合計時間:46.5H

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

Privacy Policy

Takuro Nishigai built the Flying Bat :) as a Free app. This SERVICE is provided by Takuro Nishigai at no cost and is intended for use as is.

This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.

If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Flying Bat :) unless otherwise defined in this Privacy Policy.

Information Collection and Use

For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way.

The app does use third party services that may collect information used to identify you.

Log Data

I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.

Cookies

Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device’s internal memory.

This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.

Service Providers

I may employ third-party companies and individuals due to the following reasons:

To facilitate our Service;
To provide the Service on our behalf;
To perform Service-related services; or
To assist us in analyzing how our Service is used.
I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.

Security

I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.

Links to Other Sites

This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.

Children’s Privacy

These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.

Changes to This Privacy Policy

I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page.

Contact Us

If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at habecombe@gmail.com

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