20190622のiOSに関する記事は3件です。

iOS10でもWKWebViewを使ってPOSTしたい

はじめに

初心者のハマりどころのひとつと思われるWebViewにまつわる話です(私もiOS初心者です)。
iOS10でWKWebViewを使用してPOSTでリクエストする時、httpBodyがnilになるというバグがあるようです。
バグについては、iOS10までとiOS11以降で検証されているこちらの記事が分かりやすいです。
解決策はこちらの記事を参考にしました。

前提条件

XCode 10.2.1
Swift4
DeploymentTarget: iOS10

解決案

今回は「はじめに」の参考にした記事で紹介されている、「URLSessionDataTaskで取得したデータをWKWebViewの以下のメソッドに渡す」方法で対応しました。

まずはURLSessionでリクエストを送信する処理を追加していきます。
今回はデータをjson形式にして送ります。
do-catch内でURLSessionDataTaskにリクエストの内容を渡して、リクエストを開始します。

ViewController.swift
    @IBOutlet weak var webViewContainer: UIView!
    private let url = "http://xxx.xxx.x.x"

    override func viewDidLoad() {
        super.viewDidLoad()

        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)

        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        let params: [String: String] = [
            "hoge": "hoge",
            "huga": "huga"
        ]

        do {
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
            let task = session.dataTask(with: request as URLRequest)
            task.resume()
        } catch {
            print(error.localizedDescription)
        }
    }

URLSessionDataDelegateに、URLSessionで受け取ったデータをWebViewに渡す処理を実装します。UIを更新する処理なのでメインスレッドで行う必要があります。DispatchQueueを使ってメインスレッドでWebViewのロードを行います。

ViewController.swift
extension ViewController: URLSessionDataDelegate {

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {

        DispatchQueue.main.async(execute: {
            let webView = WKWebView(frame: self.webViewContainer.bounds)
            self.webViewContainer.addSubview(webView)
            webView.load(data, mimeType: "text/html", characterEncodingName: "UTF-8", baseURL: URL(string: self.url)!)
        })
    }
}

Web側のソースです。
今回はローカルにXAMPPを入れてテストします。

test.php
<?php
    $json = file_get_contents('php://input');
    echo $json;

結果

以下のようにパラメータが表示されれば成功です。
iPhoneSE_iOS10_0.png

まとめ

iOS13が発表されましたが、iOS10がサポートされている間は、この手法を使うことがあるかもしれません。ただ私はまだiOSの経験が浅いですし、この実装方法で正しいか分かりませんので、もっといい方法があれば知りたいです。

参考

iOS(swift)ガワアプリの作成で色々苦労した話
iOSのWKWebViewの3つのつまずきポイントと解決方法
SwiftでjsonをphpへPOSTする

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

Xcode11で作成したプロジェクトを古いOSに対応させる(とりあえず版)

Xcode11 beta で新規プロジェクトを作成。
001.png

Deployment Targetを古いOSに設定すると、
0002.jpg

エラーがぞろぞろ出てくるのをなんとかしようという話。
004.png


1. AppDelegate.swift と SceneDelegate.swift
エラーが出ているfunctionに@availableを付ける。

AppDelegate.swift    SceneDelegate.swift
@available(iOS 13.0, *)

2. AppDelegateにwindowプロパティを追加する。

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow? // 追加

とりあえず、これでエラーは出なくなります。

ただし、
(iOS 13)
1. 起動
2. AppDelegate application:didFinishLaunchingWithOptions: (windowはnil)
3. SceneDelegate scene:willConnectTo:options (windowに値がセットされる)

(iOS 12以下)
1. 起動
2. AppDelegate application:didFinishLaunchingWithOptions: (windowに値がセットされる)
3. SceneDelegate 呼ばれない

このような起動時の流れになりますので、windowプロパティにget / setしている場合はもうひと工夫必要になります。


Beta版を元に作成しています。製品版リリースでは変更される可能性があります。

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

FirebaseだけでiOS版マッチングアプリ「Rose Me」をリリースするまでの話

限定公開した日付になっていたので再投稿しました泣

普段は京都大学で学生をしており、iOSエンジニアとしての歴も3年目となりました。

NoSQLもFirebaseも全く触ったことのなかった僕が、バックエンド全てFirebaseで完結させた知見を残しておきます。

何を作ったのか


ランディングページ
https://app-rose.me/

AppStore
https://apps.apple.com/us/app/rose-me/id1453049174?l=ja&ls=1

恋愛は試着する時代

という信念のもと、試着感覚のデートを提供するマッチングアプリRose Meを開発しました。

空いている時間に、好きな場所1時間だけのデートのおさそいをして、ローズを送ることでデートに行きたいという気持ちを伝えることができます。

デート後に、お互いが気になるボタンを押していれば連絡はとりあうことができ、片方でも押さなければ連絡はとれなくなるといったシステムです。

バラを実際に渡す感じを実装したのでぜひアプリで触ってみてください!

開発チーム

iOSエンジニア2人というめちゃくちゃに偏ったチームでした。

役回りとしては

僕: プロダクト設計・DB設計・Firebase周り全て・アプリ全般・年齢確認Webアプリ・書類全般・UI
相方: 「チャット・プロフィール・マイページ」等の主要画面の開発・LP作成・UI

って感じでした。

後半はZenHubを使用してWeek単位でタスク管理をして開発を進めていました。

ちなみに僕はプロダクト設計・DB設計(NoSQL)・Firebase・Webアプリ(React)は全部初めてでした。

UIは二人とも素人で、これだけを参考にして画面作ってましたww

なぜFirebaseだけでマッチングアプリを作ったのか

iOSエンジニアの友人と法人を立ててマッチングアプリを作ろうということで、2018年の10月から開発をスタートしました。

そこで直面した問題が

「バックエンドできる人いなくね?www」

でした。

「バックエンドを簡単に作れるFirebaseみたいなものがあるらしい。」ぐらいの知識でしたが、Cookpadさんの記事をみて、藁にもすがる思いで速攻採用しました。

結論から言うと、Firebaseを選択して大正解でした!!

使用した技術

Firebase

  • Authentification
    • Facebookログインに使用しました
  • Cloud Firestore
    • NoSQLのデータベース、必須です
  • Cloud Functions
    • DBの変更を監視してプッシュ通知を送ったり
  • Cloud Storage
    • プロフィール画像を保存しています
  • Hosting
    • 年齢確認用のWebアプリをのっけてます

主要なライブラリ

  • Pring: Firestoreでモデルを扱うのにめちゃくちゃ重宝しました。今はdepricatedでBallcapを使うと良いみたいです。
  • HGCircularSlider: Rose Meでは時間を視覚的に扱いたかったので採用しました。
  • RxSwift: 言わずもがな
  • IGListKit: タイムラインの差分更新するのに使用しました。
  • etc...

アーキテクチャ

開発速度を重視したかったのでMVVMを採用しました。

MVVMを触ったことがなくこれもイケるやろ!とおもって採用したら、結局MVCとMVVM足して割る2みたいなコードができあがりました。ホクホク。

苦労したこと

Cloud FunctionsがNode.jsのみ対応

GCP版のCloud Functionsを使えばNode.js・Python・Goに対応していますが、Firebase版ではNode.jsのみです。
これに気づかずTypeScriptを1から勉強して書きました。

ただし僕は、TypeScriptで書くことをオススメします。
動的型付けはかなり辛くて、ドキュメントとエディタの行き来で1日が終わります。

Cloud Functionsをローカルで実行できなかった

Firestoreの変更をトリガーにして処理をするFunctionを書いていました。
そのためデプロイして、Firestoreのフィールドを弄って、ログを見て正しく実行されているかチェックしていました。

このせいで無駄なデプロイを100回はした気がします。

ローカルエミュレータなるものがあるみたいですが、今回は諦めました。
https://firebase.google.com/docs/functions/local-emulator?hl=ja

クライアント側にFirestoreの操作を記述する必要がある

クライアント側でクエリを構成してFirestoreからのフェッチ処理を記述しているので、Android版やWeb版を作る際に通信ロジックをそれぞれに記述しなければならないことが大変。

もしマルチプラットフォームを見据えているのであればCloud FunctionsでREST APIを構成したらいいのかもしれない。

Firestoreの監視による無限ループ

これはあるあるかもしれないですが、Firestoreでの変更を監視して、そのドキュメントに対して何か書き込みをした場合、再びonUpdateが呼ばれ無限に書き込みをしてしまうことがありました。

RxSwiftでは、適切にdistinctUntilChanged()を噛ませないと大変なことになります。(アプリも、請求金額も)

テストデータを手作業で突っ込んでいた

日時を指定してデートを募集するアプリであるため、タイムラインに過去のデートは表示されない仕組みになっています。
(Develop環境は過去のデートを表示しても良かったですが、そこまで頭が回りませんでした)

毎回相方とアプリ内で操作して、募集を作って、ローズを送りあってマッチしてました。

最初の段階でCloud FunctionsでHTTPメソッド叩くだけでテストデータが生成されるような仕組みを作っておいた方がいいです!
絶対に!!

Appleの審査で必ずやりとりをしなければならなかった

合計5回ぐらいAppleに審査を出したんですが、毎回英語でメッセージのやり取りがありました。

毎回テストアカウントを用意して登録フローからレビューしてもらうのですが、

「アプリの全ての機能が使えない。」

と言われました。

というのも、年齢確認が完了していないと投稿や応募ができないため審査ができないとのこと。

やり取りした後も1-2日待たされて、審査に4日はかかります。

テストアカウントだけは年齢確認のフローは飛ばせるようにした方が審査は圧倒的に楽になります!

インターネット異性紹介事業の届出

個人的に何よりも辛かったです。

書類を10個以上用意して警察に持っていくんですが、京都ではインターネット異性紹介事業の届出の前例がなく受理できるかわからないと言われ、3日ほど待たされました:zipper_mouth:
Webアプリじゃないから固定のURLがないのでダメという理由の一点張りで、何を言っても通じませんでした。
奥の方からITに詳しい人が出てきましたが、「GCP...?」「ハッピーメールと一緒?」と言われ悲しい気持ちになりました。

よかったこと

Cloud Firestoreが正式リリースした

2019年2月にFirestoreの正式リリースが行われ東京リージョンができました。(asia-northeast1)
新規開発をする人は、Realtime DatabaseじゃなくてFirestore一択で!!

オンライン/オフラインをあまり気にしなくていい

端末自体にFirestoreのキャッシュを持っているので、キャッシュに対してクエリを実行することができます。

なので、オフライン時に投稿やプロフィールの変更を行ったとしても、次にオンラインになった時に書き込みをしてくれるのでオフラインのハンドリングに神経質にならなくてもよくなりました。

Hostingが簡単すぎる

年齢確認をするために、WebアプリをReactで作成しました。
Buidディレクトリを指定してdeployするだけでhttps://{project-id}.web.appが生成されます。

こんな感じのWebアプリを作りました。

自分たちで地道に1件1件、年齢確認をしていきます。笑

Pringが神

例えばこれだけでメッセージのスキーマが作れます。
開発者の@1amageek さんに感謝しかありません。

Message.swift
import Pring

@objcMembers
class Message: Object {
    dynamic var text: String?
    dynamic var senderRef: Reference<User> = Reference()
}

結局Firebaseはどうなのか

開発スピードとスケーラビリティを重視するのであれば自信を持ってオススメします!

iOSエンジニアしかチームにいなくて、誰もFirebaseを使ったことがなくても全然なんとかなります。

Firebaseとの連携作業も慣れれば10分で終わります。

ひとこと

今日の朝シャワーを浴びながら思ったことを書こうと思います。

技術しか知らなかった僕が去年の6月に会社を立てて、マッチングサービスを出す!
と意気込んでからちょうど1年がたったのかとシャンプーをしながらしみじみと感じていました。

思えばDeNAのインターンに行ってからユーザーのニーズを考え抜いてサービスを作る大事さ、楽しさを知ったなと思ったり。

デザイナーさんを雇うお金もないのでダサいUIだったのですが、たまたま東京で会うことのできたデザイナーさんにしつこく画面を見せてレビューしてもらったり。(あの時は本当にありがとうございます。)

男だけで恋愛バラエティをみてこんなデートいいなぁ〜っていいながら着想を得たり、毎日大学にこもって開発したり。

「マッチングはレッドオーシャンだから難しい。」
「試着できるデートは斬新で面白そう。」
「ネットで出会うなんてあり得ない。」
と色々な意見をもらって、自信がついたり無くしたりを繰り返していました。

走馬灯のように色々思い出す中で、どれだけの人が自分たちのサービスを使ってくれるのか、パートナーが見つかるのか、不安とドキドキでいっぱいです。

Qiitaにそぐわない記事になってしまいそうなので、ここらへんにしておきます。

名前は伏せさせていただきますが、Rose Meを考案するにあたって様々な方にアドバイスを頂き、無事リリースすることができました。
本当にありがとうございます!

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