- 投稿日:2019-05-06T23:08:09+09:00
macOSで隠しファイルを編集する!
ホームディレクトリの中ある".bash_profil"にコードを書いて使えるようにします。このファイルは普段表示されていません。そこで表示させましょう!
"⌘(コマンド)"と"シフト"と ".(ドット)"を表示したいフォルダを開いて、有効になっている時に同時に押してみましょう。
".(ドット)"から始まるファイルが表示されるはずです。こうなれば普通にファイル編集が可能になるので、".bash_profil"などもCotEditerなど通常使っているものを使えます。
今回はmacにデフォルトで入っているPythonは2.7ですがエイリアスを指定してPython 3.7を"python"のコマンドで起動するようにします。
.bash_profilにalias python=python3 を記述します。これだけです。
ターミナルで"python"と打つと、python3.7が起動しました!
- 投稿日:2019-05-06T23:05:57+09:00
ラベルの一部分の色を変える(Swift)
ラベルの色を部分的に変えたい場合、少し調査が必要だったのでまとめます。
主に2パターンの方法があります。その1 StroryBoardを使う
この方法だと、コードを書かなくていいので、設定が簡単にできます
対象のラベルを選択した状態にします。
①TextをAttributedにします
②ラベルに表示する文言の色を変えたい部分だけ選択した状態で色を変える(赤枠の部分です)これだけで部分的に色を変えれます。
その2 ソースコードで定義する
この方法だとStoryboardの設定は不要ですが、実装が必要です。
まずは、ラベルとソースコードでの定義を紐付けます
上記のように黒い丸が付いていれば紐付けされているはずです。
次に、ラベルの設定をします。
controller.swiftlet attrText1 = NSMutableAttributedString(string: taskLimitLabel.text!) attrText1.addAttributes([ .foregroundColor: UIColor.red, ], range: NSMakeRange(6, 9)) taskLimitLabel.attributedText = attrText1上の処理では属性を加える文字列を定義して、addAttributesで属性を加えています。
今回はrangeで色を変える文字が何番目かを設定し、foregroundColorで色を設定します。
最後に、属性を追加した文字をラベルに置き換えます。
これで一部分の色が変わります。今回は2パターン記載しました。
実装でカスタマイズする方法は他にもあると思います。
個人的にはStoryboardを使ったほうが視覚的に見やすいと思いますが、複数人で開発など、ソースで書いた方がいいパターンもあると思うので、場合によって使い分けたいと思います。参照
- 投稿日:2019-05-06T22:35:07+09:00
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 #endifUIKit / 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 メソッドを使用することで、ターゲットとアクションの設定を、プラットフォームの差を意識せず実施できるようになる。
- 投稿日:2019-05-06T22:03:38+09:00
アクション付き通知を作ってみる
はじめに
通知に対するアクションを実装したくて書きました.
LI◯EやMESSE◯GERの通知を3Dタッチした時に出てくるものを想像していただければ大体合ってます.基本的にはローカル通知の実装と一緒です.通知許可のリクエスト
まずはUserNotificationsのimport
AppDelegate.swiftimport UserNotificationsiOS9, iOS10以降で分けてリクエスト
通知許可ダイアログの表示AppDelegate.swiftfunc 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つ用意して,対応したアクションでカウントアップさせます.ViewControllerimport 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() } }完成図
参考記事
- 投稿日:2019-05-06T21:46:43+09:00
【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 らしいコードが記述できそうです。
- 投稿日:2019-05-06T21:43:24+09:00
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 updateSwift 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.swiftimport 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]/helloworldCloud 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
と返ってきます。
- 投稿日:2019-05-06T19:29:41+09:00
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) // => 21mapや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)] * ] */まとめ
以上です。競技プログラミングは文法が分かってやっとスタートライン。
あとはアルゴリズム力を存分に発揮して問題を解いて下さい。
- 投稿日:2019-05-06T13:04:55+09:00
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) }
- 投稿日:2019-05-06T12:13:47+09:00
使いやすい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 // ユーザが閉じたあとに行う処理を記述 } } }
- 投稿日:2019-05-06T11:38:14+09:00
【学習記録16】2019/5/6(月)
学習時間
5.0H
使用教材
・改訂新版Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus
学習分野
第2章変数、定数と基本的な型
第3章制御構文コメント
学習開始からの期間:16日目
今日までの合計時間:46.5H
- 投稿日:2019-05-06T01:06:13+09:00
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