[JS] スマホブラウザでズームしてもスクロールしても下に居座り続けるボックスを作る

こんにちは。カイザーです。
今日はJSです。

以前、拡大操作が前提のスマホ用Webサイトに、画面下に固定の情報を置く必要が出てきました。

拡大できないWebサイトや、PCサイトの場合、単にcssで「position: fixed;bottom: 0px;」とするだけでOKなのですが、これだとスマホで拡大するとレイアウトが崩れてしまいます。

これには現状では対応できないようなので、JSで対応しました。

それで、下記を参考に作ってみました。

スマホ対応の参考に。JavaScriptでウェブページがどのくらい拡大されているのかを取得する方法。

実際には「updateInfoPosition()」で「#information」の位置を、現在のスクロール・拡大率を元に更新しています。また、y座標は計算が複雑だったので、「getScrollBottom()」に分けてあります。

また、JSでのDOMを使った描画更新は重いので、タッチ時・ジェスチャー時は「#information」を隠し、それが終わった1秒後に表示するようにしています。1秒間は、スマホブラウザ特有の惰性移動のため、タイマーで待ってもらっています。

[iOS] Googleカレンダーのログイン認証をGIDSignInに移行する

こんにちは。カイザーです。

今年の4月にWebViewを使用した従来のGoogleログインは使用できなくなり、GIDSignInなどを使用する必要が出てきました。
WebViewでは、普段ユーザが使用しているログイン情報を使用することができず、アプリ毎にユーザがログイン情報を入力する必要があり、煩わしいためです。
そこで、URLスキームを使って普段使用するWebブラウザを使用させるか、WKWebViewなどのWebブラウザとログイン情報を共有出来る方法に移行するわけです。

GIDSignInは、この新しいログイン周りが簡単に行うことができるGoogle公式のライブラリです。WKWebViewを使用します。
今回は、従来の認証からGIDSignInに移行し、Googleカレンダーで使用する、という想定で進めていきます。

1. まずは導入

下記URLに従って導入します。とりあえずログインは出来るようになります。
https://developers.google.com/identity/sign-in/ios/sign-in?ver=swift
その際、以前の「GTMOAuth2ViewControllerTouch」でログインしていた処理は削除してください。

2. ログイン結果を通知

以前は、該当のViewControllerでログイン結果を受け取ることができましたが、今回はAppDelegateで行うようになっています。
そのため、AppDelegateからNotificationを使ってViewControllerに受け渡します。

AppDelegate.swift

extension Notification.Name {
static let googleSignInSuccess = Notification.Name("googleSignInSuccess")
static let googleSignInFailed = Notification.Name("googleSignInFailed")
}

AppDelegate.swift

func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!,
withError error: NSError!) {
if let _ = error {
// サインインが失敗
NotificationCenter.default.post(name: .googleSignInFailed, object: nil)
} else {
// サインインが成功
NotificationCenter.default.post(name: .googleSignInSuccess, object: nil)
}
}

これで、サインインできた時に通知するところまではできました。

3. ログイン結果の受け取り

あとは、受け取る側のViewControllerでNotificationの受け取りを設定します。失敗の場合はエラー表示します。
LoginViewController.swift

var service: GTLServiceCalend

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loginSuccess), name: .googleSignInSuccess, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(loginFailed), name: .googleSignInFailed, object: nil)
}

deinit {
NotificationCenter.default.removeObserver(self)
}

func loginSuccess() {
// ログイン成功
}

func loginFailed() {
let alert = UIAlertController(title: "ログインエラー", message: nil, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
present(alert, animated: true, completion: nil)
service?.authorizer = nil
setGoogleAccountLabelText(nil)
}

4. カレンダーAPIのリクエスト


// これは以前と同じです
private let service = GTLServiceCalendar()
func fetchEvents() {
// セッション切れになっているかもしれないので、再ログインしておく
GIDSignIn.sharedInstance().signInSilently()
if let auth = GIDSignIn.sharedInstance().currentUser {
// 以前「GTMOAuth2ViewControllerTouch」から取得していた認証情報は、「auth.authentication.fetcherAuthorizer()」で取得できる。
service.authorizer = auth.authentication.fetcherAuthorizer()
}
// カレンダーのイベントを取得します。service.authorizerに認証情報が入っているため、以前と同じように使えます。カレンダーイベント取得後「finishedWithObject」が呼ばれます。
let query = GTLQueryCalendar.queryForEventsList(withCalendarId: "カレンダーID")
let calendar = Calendar.current
var components = (calendar as NSCalendar).components([.day, .month, .year], from: selectedDate)
components.month? -= 1
var date = calendar.date(from: components)
query?.timeMin = GTLDateTime(date: date, timeZone: TimeZone.autoupdatingCurrent)
query?.singleEvents = true
query?.orderBy = kGTLCalendarOrderByStartTime
service.executeQuery(
query!,
delegate: self,
didFinish: #selector(CalenderViewController.displayResultWithTicket(_:finishedWithObject:error:))
}

ここでポイントになるのが、以前から使用していた「GTLServiceCalendar().authorizer」と、「GIDSignIn.sharedInstance().currentUser.authentication.fetcherAuthorizer()」が同じinterfaceを適用しているので、キャスト可能であるということです。

これは特に文献がなかったので、宣言部をひたすら見て行くしかなかったですね。。(GTLSercieとGIDSignInは両方ともObjective-Cで書かれているので、ヘッダファイルを見ていきます。)

まとめ

GIDSignInに切り替えても、従来のGTLServiceは利用可能。
ちなみに、AndroidはWebViewログインを行わず、OSの機能でログインできるので対応は不要です。

GoogleのAPIを使用していて、まだWebViewで認証している方は、急いで移行してください!

Swiftのmap、filter、forEachでワンランクアップ!

お久しぶりです。カイザーです。

さてみなさん、Swiftしてますか?
いいですよね、Swift。

今日は、Swiftのmap、filter、forEachについて紹介します。

まず概要についてはこちらです。
http://qiita.com/motokiee/items/cf83b22cb34921580a52#enumerate要素と要素のインデックスが欲しい時に使う

つまるところ、これまでforinなどの高速列挙を使って、配列の要素に対して、統一した処理を行ったり、条件分岐して絞り込みを行ったり、、、みたいなことが簡単にできます。

ここでサンプルです。


self.stamps = resultStamps.filter({ (element) -> Bool in
element.category == .normal
})

あるコントローラは「stamps」という配列を持っていて、それは「category」というプロパティを持っています。
その「category」がnormalのスタンプのみを絞り込んでいます。

先ほどの記事の例では数字に関するものが多かったですが、このように、モデルクラスに対しても便利に使うことができます。

そのほかにも、filterはこんな風にも使えます。


self.stamps.filter({ stamp -> Bool in
stamp.count > 0
})

先ほどのスタンプで、所持しているものがフィルターされます。

このような感じで、Swiftでは少し気をつけると、かなり簡潔にプログラミングできます、

配列が関連するループでは、forinさえもあまり使わなくても良さそうですね。
ちなみに、Java 8でも同じような機能があります。Android開発でJava 8を使う際も積極的に使っていきたいところですね。

これを使って格好よくプログラミングしちゃいましよう!