- 投稿日:2019-08-20T20:04:21+09:00
iOS13(beta)でASWebAuthenticationSessionの変更点
注:本記事は
Xcode 11 beta 6
(2019/08/20)時点での変更内容での話になりますiOS13でログイン画面が開かない
開発中のアプリで
ASWebAuthenticationSession
を利用してOAuthログインする機能があり、iOS13(beta)で動作確認をしたところログインボタンを押してログイン画面を出そうとしても出てくれない状態でした。ログを見てみると以下のエラーの出力がありました。
Cannot start ASWebAuthenticationSession without providing presentation context. Set presentationContextProvider before calling -start.どうやらiOS13からは
start()
を呼び出す前にpresentationContextProvider
をセットする必要があるようです。ASWebAuthenticationPresentationContextProviding
presentationContextProvider
はASWebAuthenticationPresentationContextProviding
(document)というprotocolに適合したNSObject
のサブクラスのインスタンスのようです。定義は以下のようになっています。
/** @abstract Provides context to target where in an application's UI the authorization view should be shown. */ @available(iOS 13.0, *) public protocol ASWebAuthenticationPresentationContextProviding : NSObjectProtocol { /** @abstract Return the ASPresentationAnchor in the closest proximity to where a user interacted with your app to trigger authentication. If starting an ASWebAuthenticationSession on first launch, use the application's main window. @param session The session requesting a presentation anchor. @result The ASPresentationAnchor most closely associated with the UI used to trigger authentication. */ func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor }ASPresentationAnchor
なるほど、そのprotocolのインスタンスを用意すればいいことはわかったけど、
ASPresentationAnchor
ってなんだ?と思ったらUIWindow(iOS, Mac Catalyst, tvOS)
orNSWindow(macOS)
のtypealiasでした。直してみる
シンプルに書くと多分こんな感じに実装すれば良さそう
import AuthenticationServices import UIKit class ViewController: UIViewController { private var authenticationSession: ASWebAuthenticationSession? // ログインボタンを押した後に呼ばれる func loginStart() { let authenticationSession = ASWebAuthenticationSession( url: URL(string: "https://...")!, callbackURLScheme: "<url-scheme>", completionHandler: loginCompletionHandler(url:error:)) if #available(iOS 13.0, *) { // authenticationSession.presentationContextProvider = self } authenticationSession.start() // authenticationSessionが解放されないようにプロパティに入れておく self.authenticationSession = authenticationSession } // ログインの結果のハンドリング func loginCompletionHandler(url: URL?, error: Error?) { authenticationSession = nil ... } ... } @available(iOS 13.0, *) extension ViewController: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return view.window! } }ちなみに今開発中のアプリではiOS11も対応しており、
ASWebAuthenticationSession
とSFAuthenticationSession
をラップしたクラスを定義していたため、ASWebAuthenticationPresentationContextProviding
を適合させたprivateなクラスを用意して実装しました。
このように実装する場合、presentationContextProvider
は弱参照なので解放されてしまいます。そのためASWebAuthenticationPresentationContextProviding
を適合したオブジェクトのインスタンスもプロパティとして保持しないといけないことに注意してください。class AuthenticationSession { ... private var authenticationSession: Any? private var presentationContextProvider: Any? func loginStart(from viewController: UIViewController) { if #available(iOS 12.0, *) { let authenticationSession = ASWebAuthenticationSession( url: loginURL, callbackURLScheme: "<url-scheme>", completionHandler: loginCompletionHandler(url:error:)) if #available(iOS 13.0, *) { let presentationContextProvider = AuthPresentationContextProver(viewController: viewController) authenticationSession.presentationContextProvider = presentationContextProvider self.presentationContextProvider = presentationContextProvider } authenticationSession.start() self.authenticationSession = authenticationSession } else { // iOS 11.x let authenticationSession = SFAuthenticationSession( url: loginURL, callbackURLScheme: "<url-scheme>", completionHandler: loginCompletionHandler(url:error:)) authenticationSession.start() self.authenticationSession = authenticationSession } } func loginCompletionHandler(url: URL?, error: Error?) { authenticationSession = nil presentationContextProvider = nil ... } ... @available(iOS 13.0, *) private class AuthPresentationContextProver: NSObject, ASWebAuthenticationPresentationContextProviding { private weak var viewController: UIViewController! init(viewController: UIViewController) { self.viewController = viewController super.init() } func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return viewController?.view.window! } } }おわり
ASWebAuthenticationSession
を使っているアプリはiOS13が出るまでに対応しないとログインが出来なくなってしまう可能性があるので注意しましょう。
- 投稿日:2019-08-20T16:55:16+09:00
[30秒で理解]SwiftでDI(Dependency Injection)
最近、少しばかりKotlinでAndroid開発をしているときにSwift以上にKotlinの方がDIの文化が進んでそうだな(主にDaggerとか)と思ったんですが、意外とちゃんと時間をとってSwiftのDIについて調べていないなと思ったので時間をとって記事にした次第です。
Dependency Injectionとは
名前の格好良さとか複雑さに惑わされて欲しくはないので言いますが、
ただ外部から値を渡して上げて受け取った物(主にclass)は受け取ったことを前提に記述することできる
ただそれだけです。
実際にcodeに起こしたときはこんな感じです。
class Hoge { var name: String init(name: String) { self.name = name } }let hoge = Hoge(name: "hogehoge")これが一番シンプルなDIと言えると思います。DIってなんだろうと思って検索した場合は、これは当たり前のことだし、これをDIっていうの思った人も少なくないと思います。ライブラリを使うと
コンテナ
とかファクトリ
とかいろいろな単語が出てきて混乱するかもしれませんが、DIという手法をより有効的に使用するための要素でその手法本体ではありません。それでもまだ信じられない人はこの記事をみてみることを推奨します
https://qiita.com/ostk0069/items/03c968bacf8e1372b7e7
この記事は以前私が書いた物ですが、
UITableView
を使うときにUITableViewCell
をdequeue
(呼び出す)するタイミングでパラメータを挿入しているだけです。上のサンプルコードでもname
を挿入しているのと同様です。しかし、これだけではおわりません。いまの説明で完全に理解するにはDIのDの方、
Dependency
への理解が不十分です。ここでいうDependency
とはSingleton
であると考えて良いです。Singletonについて
Singleton
とはその物(class, protocol)が実行時に一つのオブジェクトにのみ依存することを約束することです。ここでSwiftにおいてSingletonを約束するために必要なのがコンテナという概念です。これ以上深く話すには実際にDIのライブラリ等でDIのコンテナの解釈のもと、ライブラリが作られているかで少し変わってきます。思想の問題です。Swiftで書かれているDIのライブラリ
コンテナを用いる場合
一番使われれているであろうライブラリ
https://github.com/Swinject/Swinject使ったことないけど良さそう
https://github.com/ivlevAstef/DITranquillityコンテナを用いない場合
Uberが作成しているライブラリ(バージョンが1.0未満なので安心して使えるか微妙)
https://github.com/uber/needle星の数は多いけど少し前から更新が止まってるので不安(8/20時点)
https://github.com/square/Cleanseその他(injectionが綺麗にかけるライブラリ)
- 投稿日:2019-08-20T16:40:31+09:00
iOS13でUISegmentedControlの見た目をカスタマイズする
※ 実装/動作確認環境: Xcode 11 beta5 + Simulator
※※ iOS13正式リリース前につき、スクリーンショットは未掲載(100%見た目についての話なのでなんとも物足りない感じですが…)。背景
iOS13では
UISegmentedControl
の見た目が変更される。
それに伴い、見た目(主に色)のカスタマイズをする場合、iOS12以前とは異なる方法で行う必要がある。さしあたりの対応として行ったことをメモ。
(DarkMode対応なども鑑みるとカスタマイズなしで使うようにUIを設計し直すのが妥当とは思うが…)iOS12以前
tintColor
がビューやラベルの色に適用されるため、ここに任意の色を指定するだけでOKだった。tintColor = UIColor.orangeiOS13
デフォルトの表示が無彩色となり、
tintColor
に依存しなくなった。以下の方法でそれなりにカスタマイズできる。しかしそれぞれ一長一短あり。
selectedSegmentTintColor
を使用する方法
tintColor
ではなくselectedSegmentTintColor
を指定することで、選択中のセグメントの背景色を設定できる。
また、文字色はデフォルトで黒色であるため、設定する背景色によっては文字色も変更したほうがベター。func configure() { let color = UIColor.orange selectedSegmentTintColor = color setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .selected) setTitleTextAttributes([NSAttributedString.Key.foregroundColor: color], for: .normal) }メリット
コード量も少なく、やっていることも明確である点。
基本的にはこの程度のカスタマイズで収めたい。デメリット
この方法ではビューの背景にある薄いグレーはそのままなので、
selectedSegmentTintColor
の明度によってはコントラストが弱くなる恐れがある。
(View Hierarchyを確認したところ、SegmentControlのsubview内に半透明のグレー画像が設定されたUIImageViewがある模様)コントラストに懸念があり、かつ色も変更できない場合は次の方法で薄いグレーの背景を除去するのがよさそう(ただし急場しのぎ。詳細は後述)。
setBackgroundImage()
を使用する方法func configure() { let color = UIColor.orange // 背景を単色画像に差し替え setBackgroundImage(UIColor.clear.toImage(), for: .normal, barMetrics: .default) setBackgroundImage(color.toImage(), for: .selected, barMetrics: .default) // ラベルのスタイルを設定(選択中のセグメントのラベルがBoldでなくなるので、合わせて設定) setTitleTextAttributes( [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 13.0), NSAttributedString.Key.foregroundColor: UIColor.white], for: .selected) setTitleTextAttributes([NSAttributedString.Key.foregroundColor: color], for: .normal) // グレー領域がなくなったことで境界がわかりづらくなるので、ボーダーを追加 layer.borderColor = color.cgColor layer.borderWidth = 1.0 }↑で使用している
UIColor.toImage()
は1x1の単色UIImageを生成する拡張メソッド。実装方法はこちらの記事等を参照。メリット
背景のグレーがなくなるので、SegmentedControlの色をより自由に決められる点。
デメリット
ビューの構造も変わってしまうらしく、見た目はiOS12以前のSegmentedControlのようになってしまう。
また、たかだか6ステップ程度ではあるが、先の方法よりは回りくどい。
そうまでしてiOS12以前っぽい見た目にするのか?となるので、やはりiOS13標準に則ったUIに変更するのが真っ当だと思う。参考
- 投稿日:2019-08-20T16:16:40+09:00
swiftでポップアップの値渡しを簡単に実装する方法
はいどうもこんにちは
相変わらずGithubがろくすっぽ使えませんが何とか生きています
いつもハマった話ばかり書いていますが、今日は別にハマっていないけど、デリゲートでコールバックするより綺麗だなと思ったので書きます!
デリゲートは沢山コールバックをするならひとまとめにできるのでわかりやすいのですが、一つコールバックしたいってくらいだとこっちのがシンプルで楽な気がします。
特にポップアップが閉じられた時にコールバックしたことってよくあると思うのですが、今回はそれです。
方針としては
1. ポップアップのコントローラに"メソッドの入れ物"を配置
2. ポップアップを開く前のコントローラに、ポップアップを開くときのコードと、ポップアップが閉じたあとの処理を実装
3. ポップアップのコントローラに閉じるボタンを押されたときの処理を実装こんな流れです
それではまずは
1. ポップアップのコントローラに"メソッドの入れ物"を配置
メソッドの入れ物です。今はまだ空っぽです。
これを置いておくことで、渡されたメソッドをポップアップのクラスで実行することができます。PopupVC.swiftclass PopupVC: UIViewController { // メソッドの入れ物みたいなものです var emptyClosure: (() -> Void)? }2. ポップアップを開く前のコントローラに、ポップアップを開く前の処理と閉じた後にやりたい処理を実装
特筆すべき点はありませんが、ポップアップのVCの開き方はケースバイケースなのでよしなにお願いします。
SomeVC.swiftclass SomeVC: UIViewController { func showPopup(){ let pupupVC = (略)(何かしらの方法でポップアップのコントローラをここに) // ここで入れ物にメソッドを渡します pupupVC.emptyClosure = {self.hogeMethod()} // (ここで何かしらの方法でポップアップを開く処理を書きます) } func hogeMethod(){ // (ポップアップが消えたときにやりたい処理) } }3. ポップアップのコントローラに閉じるボタンを押されたときのコールバック処理を実装
既に2.で空だった入れ物にはメソッドが入りました。
そしてここでdismiss()のcompletionにて、閉じるときにSomeVCで定義したメソッドを発動します。
PopupVC.swiftclass PopupVC:UIViewController { // 空の入れ物でしたが、既にこいつはメソッドをもっています var emptyClosure: (() -> Void)? @IBAction func closeBtnAction(_ sender: UIButton) { self.dismiss(animated: false, completion: { // UIViewControllerが閉じられると共に実行します self?.emptyClosure!() }) } }こんな感じでシンプルにコールバック処理が書けました。
コールバックの量が多い場合はdelegateを使った方が保守性は高いかもしれませんが、簡単なものならこれでもいいですね
- 投稿日:2019-08-20T11:03:57+09:00
SwiftでもPythonみたいに文字列操作したい!
最近使い始めたSwiftでもPythonみたいに文字列操作したい!
SwiftでもPythonみたいに文字列のスライスしたいなぁ
ってことでStringを拡張してPythonの文字列操作メソッドを生やすライブラリを作ってみました。
SwiftyPyString
https://github.com/ChanTsune/SwiftyPyString
Swift5で動きます。
Unicode準拠 & Pythonのドキュメント準拠で作ってます。インストール
CocoaPods、Carthage、SwiftPMの主要なパッケージマネージャ3つに対応させています。
CocoaPods
pod 'SwiftyPyString'Carthage
github 'ChanTsune/SwiftyPyString'
SwiftPM
import PackageDescription let package = Package( name: "YourProject", dependencies: [ .Package(url: "https://github.com/ChanTsune/SwiftyPyString.git", from: "1.0.1") ] )文字列操作
添字アクセス
let str = "0123456789" str[0] // 0 str[-1] // 9Python準拠なので負の数を利用した後ろからのアクセスもサポートしています。
文字列のスライス
let str = "0123456789" str[0,5] // 01234 str[0,8,2] // 0246 str[nil,nil,-1] // 9876543210やりたかったスライスです。
一度これに慣れると他の言語でも使いたくなるやつです。
(だからこれを作ったわけですが笑)コロンだけの省略記法は
nil
で代用することにしました。ちなみに同じ動作をPythonで書くと以下のようになります。
str = "0123456789" str[0:5] # 01234 str[0:8:2] # 0246 str[::-1] # 9876543210文字列検索
// 先頭からの検索 "123412312312345".find("123") // 0 // 開始位置を指定して検索 "123412312312345".find("123",start:2) // 4 // 終了位置を指定して検索 "123412312312345".find("123",end:1) // -1 // 末尾からの検索 "123412312312345".rfind("123") // 10末尾からの検索も同様に開始位置と終了位置を指定して検索できます。
文字列結合
let array = ["abc","def","ghi"] "".join(array) // "abcdefghi" "-".join(array) // "abc-def-ghi" "++".join(array) // "abc++def++ghi"トリミング
// 右端のみ "rstrip sample ".rstrip() // "rstrip sample" "rstrip sample ".rstrip("sample ") // "rstri" " rstrip sample".rstrip() // " rstrip sample" // 左端のみ " lstrip sample".lstrip() // "lstrip sample" " lstrip sample".lstrip(" ls") // "trip sample" "lstrip sample".lstrip() // "lstrip sample" // 両端 " spacious ".strip() // "spacious" "www.example.com".strip("cmowz.") // "example"文字列分割
行ごとの分割
"abc\nabc".splitlines() // ["abc", "abc"] "abc\r\nabc\n".splitlines() // ["abc", "abc"] // 改行文字を残して分割 "abc\nabc\r".splitlines(true) // ["abc\n", "abc\r"] "abc\r\nabc\n".splitlines(true) // ["abc\r\n", "abc\n"]指定文字での分割
"a,b,c,d,".split(",") // ["a", "b", "c", "d", ""] "aabbxxaabbaaddbb".split("aa") // ["", "bbxx", "bb", "ddbb"] // 分割の回数を指定 "a,b,c,d,".split(",", maxsplit: 2) // ["a", "b", "c,d,"]出現回数カウント
"abc abc abc".count("abc") // 3 // 開始位置の指定 "abc abc abc".count("abc", start:2) // 2 // 終了位置の指定 "abc abc abc".count("abc", end:1) // 0ゼロ埋め
"abc".zfill(1) // "abc" "abc".zfill(5) // "00abc" // 符号付きの場合 "+12".zfill(5) // "+0012" "-3".zfill(5) // "-0003" "+12".zfill(2) // "+12"符号付きの場合は符号の後ろにゼロが入ります。
さいごに
以上、簡単に主要な機能の説明をさせて頂きました。
紹介したメソッド以外にもPython3.7.3の時点で利用できるstr型のメソッドは言語機能的に実装出来ない、あるいは実装が難しいもの以外はほとんど実装してあります。
実装してあるメソッドの一覧は、こちらをご覧ください。
https://github.com/ChanTsune/SwiftyPyString/blob/master/README.md一部、標準のメソッドと機能がかぶるものもありますが、PythonからSwiftに移植したいなんて言う事があれば多少は移植作業が楽になるのではないでしょうか?(普通にPythonを動くようにした方が多分楽 笑)
そうでなくともPythonからプログラミングを始めたという人なら、慣れ親しんだPythonの文字列操作ができるようになるので比較的便利ではないでしょうか?
このメソッド実装できるよ、とかこっちの実装の方がパフォーマンスいいんじゃない? Swiftだったらこう書くと綺麗だよ等ありましたら教えてください。
プルリクお待ちしております。
もしあれば、バグ報告とかも嬉しいです。
以前、C++版も作っているのでこちらも宜しければ
c++でもpythonのstr型のメソッドを使いたい!
https://qiita.com/ChanTsune/items/38814ca81738877c51fe