Sansanのモバイルアプリ開発1dayインターンに参加してきました

7月20日に、青山のSansanオフィスで行われたモバイルアプリ開発の1dayインターンに参加してきました! アプリメインのイベントに参加するのはなにげに初めてでしたが、とても充実した時間でした。

どんな人が参加していたか

書類選考があったので、来ていたのはモバイルアプリ開発AndroidiOS)の経験者のみでした。
どれくらいやってるかは人それぞれでしたが、個人で開発して個人でリリースしてた人が多かった気がします。 中にはめっちゃいろんなアーキテクチャ知ってたり、課題制作1時間もかからずに終わって周りの人にめっちゃ教えてたりするツワモノも。すごい。

インターンの内容

名刺管理サービスで有名なSansanさんの企画したインターンということで、
課題は名刺を撮影しOCRした結果を表示するアプリを作ることでした。

Googleが提供しているCloud Vision APIを使って撮影した写真のテキスト検出を行います。

ちなみにこのCloud Vision API、結構精度が高くかなり優秀です。
テキスト検出以外にもいろんな画像処理のAPIが提供されています。

cloud.google.com

午前中は今回使うAPIアーキテクチャ(MVP)に関する講義、午後はずっと開発という感じ。
基本は個人開発でしたが、iOSiOSでグループに固められているので、わからなければ周りの人に聞けるし、メンターのエンジニアの方も声かけたらすぐにとんできてくれます。

途中から自分の好きなライブラリ入れて好きなアーキテクチャでやってもいいよーって言われて、使い慣れてるMoya使って通信をやってたのですが、APIKeyをうまくURLにエンコードできない問題にハマり、しかもその原因をなかなか突き止められずめちゃめちゃ時間がかかってしまった、、、。
そのときデバック一緒にやってくれたエンジニアの方が、ライブラリの中にブレイクポイント置いて原因を見つけてくださったのがめちゃありがたかったです。
私ならそんなとこで止めようとか多分思いつかなかった。

ちなみに開発中はお菓子もジュースも飲み食べ放題でした。最高。

懇親会

一通り開発した後は、オードブルとお酒で懇親会。

今までアプリやってる人達だけで話すということがなかったのですが、同年代の学生が個人で作ってるアプリ見してもらったり、社員の方からDI・DIコンテナの良さを教えてもらったりとアプリ開発するにあたってめちゃいい刺激を受けられたと思います。

女性のiOSエンジニアの方もおられて、RxSwiftの勉強するのにマーブルダイアグラム見るのめっちゃ良いよねって話で盛り上がったの超楽しかった。
iOSDCでの登壇が決まってるそうなので、絶対聞きに行こう。

長いので中身は割愛しますが、CTOの方からめちゃめちゃありがたいお話も頂いて、とても充実した時間となりました。

Sansanの皆様本当にありがとうございました!!

余談

青山は私みたいな若造が行くにはちょっと大人な街で、勝手に気後れしてました。
がしかし、来週また青山に行く予定です。
今度はZOZOテクノロジーズさんのミートアップにいってきます!

Coderetreat for Girlsに参加してきました!

うちの大学で

Coderetreat for Girls

というイベントが行われたので参加してきました!
こちらのイベントは、enPit事業の女性部会WiTによって開催されたもので、今回で3回目なんだそう。
友達に誘われて初めて参加しましたが、とても密度の濃い時間を過ごせたと思います。 イベントの概要と感想をざっくり記録しておきます!

Coderetreatとは?

Coderetreat(コードリトリート)は、ソフトウェアの開発と設計の基本に焦点を当てて集中的に練習をするための、プログラマのための終日イベントです。 2009年に米国で発祥し、今や世界中の各地で開催されており、毎年秋にはGlobal Day of Coderetreatが世界中で一斉に開催されます。 開発者はCoderetreatに参加することで、「完成させなきゃ」という圧力から引き離され、練習に集中できます。

Coderetreatではライフゲームといった単純なお題に、45分のセッションを繰り返し取り組みます。すべてのセッションはペアプログラミングやモブプログラ ミングで行われ、セッションごとに新しい人と組み直します。書いたコードもセッションごとにすべて消します。

イベントページより引用)

当日の流れと内容

タイムテーブル

09:00 - 09:30 開場

09:30 - 10:00 趣旨説明、準備

10:00 - 11:00 セッション#1

11:00 - 12:00 セッション#2

12:00 - 13:00 ランチ

13:00 - 14:00 セッション#3

14:00 - 15:00 セッション#4

15:00 - 15:30 おやつ

15:30 - 16:30 セッション#5

16:30 - 17:30 セッション#6

17:30 - 18:00 クロージング

内容

各セッション振り返り

  • セッション#1
    言語:Python
    アクティビティ:なし
    ペアの相手は学部の男の子でした。共通言語がPythonしかなかったのでPythonを選択。
    一回目だったので、そもそもライフゲームって何?ってところを理解するのにかなり時間が取られ、完成せず。

  • セッション#2
    言語:Swift
    アクティビティ:ドライバー
    ペアの相手はランチを提供していただいたクラウドネイティブさんのエンジニアの方。セッション1での状況を伝えたら、一番得意な言語でまずはアルゴリズムちゃんと理解しようということでSwiftで書くことになりました。
    アプリまで落とし込めたらいいねなんて話してたけど、さすがに45分でそこまでは行けず。でもここで割といいところまでいけました。 この回ご一緒した方がファシリテーション上手すぎて感動。

  • セッション#3
    言語:Python
    アクティビティ:ドライバー
    ペアの相手はenPit関係者の女性の方でした。だんだんライフゲームというものを掴んできたので最初にできなかったPythonで再トライ。
    ペアの方が実装するべきメソッドを整理して紙に書いてくださったおかげでわりとさくさく進んで、ほぼ完成までいけました。
    アルゴリズム考えるの苦手だけど、一旦紙とかに書いて整理するのめっちゃ大事。

  • セッション#4
    言語:C
    アクティビティ:1メソッド4行以内
    この回は同じ大学の3年生×2と3人でモブプログラミングやりました。みんな前のセッションでやってきた言語全然違ったので、3年生の子のうちの1人がドライバーとしてCで一旦普通に実装して、それを1メソッド4行以内の条件で書き換えてみるというのをやりました。
    普段eurekaのSwiftコーディング規約に則ってスペースとか1行空けるとかめちゃめちゃ意識してるせいか、きつきつに詰めて書かれたコードはかなり違和感を感じた、、、

  • セッション#5
    言語:Ruby
    アクティビティ:ドライバー
    ペアの方は一般参加の女性エンジニアの方。やってみたい言語とかでいいよって言われて、これからインターンで書くことになるであろうRubyを選択しました。
    文法とかふたりともほぼ知らない状態だったけど、ペアの方も一緒にいろいろ調べてくださって、なんとかRubyライフゲーム完成したのかなり嬉しかったです。

  • セッション#6
    言語:Python
    アクティビティ:ループ禁止
    最後のペアは男性のエンジニアの方でした。最後なので既に実装できた言語で何かアクティビティに挑戦したいという話をしたら、Python再帰関数を使ってループ処理をする方法を提案してくださいました。派生して他にもいろんな書き方教わってたらライフゲームは全然実装できなかったけど、とても良い時間でした。なんとペアの方が数学科の出身らしく(しかも専門代数幾何だったらしい、すごい)振り返りタイムに数学の話もできたのは楽しかったです。

感想

  • ファシリテーターの皆さんが優しくて、やったことない言語・書き方でも安心して挑戦できたのが良かった。
  • 学生の皆さん意識が高くて、かなり良い刺激を受けた。
  • 美味しいランチとおやつが無料で提供されて最高だった。
  • 学生同士だと、非情報系としてどうしても引け目感じて思ってることとかはっきり言えなかったのは反省点。
  • 次回も是非参加したい!

最後に

今回のイベントを主催してくださったWiT様、スポンサーの株式会社ねこじゃらし様、株式会社クラウドネイティブ様、サイボウズ株式会社様、ありがとうございました。 とても充実した時間となりました。

UINavigationControllerに関するちょっとした躓き

インターン先でやってる開発の中で、
UINavigationControllerに関してちょっと立ち止まったところがあったので
メモしておきます。

躓きポイント

ページングビューを簡単に実装できるParchmentというライブラリを使ったPagingViewControllerをNavigationControllerの元に配置しようとしたら、対象のViewがNavigationBarの下に潜り込んでメニューバーなどが見えなくなりました。
これを解消するべく調べてみたら以下の記事がヒット↓

qiita.com

// PagingViewController

override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.navigationController?.navigationBar.isTranslucent = false
    }

これを参考にPagingViewControllerにこんなコード追加したらとりあえず直ったのですが、isTranslucentってプロパティは何ぞや?ということで少し調べてみました。

UINavigationController.navigationBar.isTranslucentについて

これはUINavigationBar、のプロパティで、ナビゲーションバーが半透明になったときにナビゲーションバーの下のコンテンツを表示するように設定します。デフォルト値はtrueです。

developer.apple.com

このデフォルト値がtrueなのにも関わらず、普通にナビゲーションバーを設置しても後ろが透過されてるように見えないのは、

  • レイアウトの中に不透明なナビゲーションバーが含むかどうかを示すextendedLayoutIncludesOpaqueBarsというプロパティのデフォルト値がfalseである
  • 通常SafeAreaはバーの下から設定されている
  • ナビゲーションバーの後ろはデフォルト背景が設定されている といった理由があります。

つまり、isTranslucentがtrueであることを活かしNavigationBarを透過させて下のコンテンツを表示するには、まずextendedLayoutIncludesOpaqueBarsプロパティをtrueにする必要がありそうです。 実は通常はこの設定だけでナビゲーションバーの下のコンテンツが見えるようになるんですが、それが叶うのはedgesForExtendedLayoutというプロパティがデフォルトで.allであるということが前提としてあるためです。

edgesForExtendedLayoutは端的にいうと、Viewがどの端まで伸びているかを示すもので、以下の項目があります。

public static var top: UIRectEdge { get }

public static var left: UIRectEdge { get }

public static var bottom: UIRectEdge { get }

public static var right: UIRectEdge { get }

public static var all: UIRectEdge { get }

デフォルトは.allに設定されています。StoryBoardからも設定できて、複数項目も設定できます。全部選ぶと.allと同じとみなされるみたいです。

ここまでをまとめると、通常であればisTranslucentがtrueであってもナビゲーションバーの下にViewが潜り込んでしまうことは無いはずなんですが、外部のライブラリ使ったりすると、意図せずしてデフォルト設定が変わっちゃってたりすることもあります。 そんなときに、isTranslucentをfalseに設定してあげると、

  • ナビゲーションバーが無条件に透過されなくなる
  • ナビゲーションバーが表示されている状態では、ViewControllerのビューの描画範囲が狭くなる

となり、潜り込んじゃったViewをちゃんと見ることができるようになるみたいです。

ちなみにこれは、TabBarなども持ってるプロパティです。

注意点

この設定を変えると、該当するNavigationController下にある全てのViewに影響が出るので、設定を変えたView以外のViewでレイアウトに多少の影響が出る可能性があります。(ちなみに私はこれいじって他のViewが崩れました)
設定を全部で統一した上で開発を進めるか、viewWillAppearとかでViewを描画するたびに設定してあげるかすると良さそうです。

間違ってたり足りなかったりしたらご指摘お願いします!

TableViewで一番下まで行ったら次のページをロードする

この前、とある課題でニュースリーダーアプリ的なのを作った際、TableViewで一番下まで行ったら次のページをロードするのを実装しようとしたら、割とごちゃっとしてたので自分なりに整理しておきます。

インジケータが乗ったxibファイルを作る

下までスクロールしたときにくるくるしてるやつですね。
これをxibファイル作ってセルに配置して、TableViewにフッターとして埋め込みます。

// ArticleListViewController.swift

 override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.register(R.nib.loadingCell)
        let footerCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.loadingCell.identifier)!
        (footerCell as! LoadingCell).startAnimation()
        let footerView: UIView = footerCell.contentView
        tableView.tableFooterView = footerView
}

一番下にたどりつく前に次のapiを叩く

一番下までスクロール仕切らないところでAPI叩いて、下までたどり着くときにちょうどParseが終わってるようにします。
(つまりさっきのインジケータが日の目を見るのはほんの一瞬)

// ArticleListViewController.swift

func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let currentOffsetY = scrollView.contentOffset.y
        let maximumOffset = scrollView.contentSize.height - scrollView.frame.height
        let distanceToBottom = maximumOffset - currentOffsetY
        if distanceToBottom < 500 && tableView.isDragging {
            self.viewModel?.search()
        }
    }

リクエスト調整

このままだと一回下までスクロールしたらそのままずっとAPI叩いちゃうので、Parseが終わるまでは次のリクエスト送らないように、以下のようなStatusを作ります。

enum LoadStatus {
    case initial
    case fetching
    case full
}

最初は.initialにしておいて、非同期処理の中で状態を設定し直してあげます。

// ArticleListViewModel.swift

func search() {
        
        guard loadStatus == .initial else {
            return
        }
        self.loadStatus = .fetching
        self.shared.sync(query: self.searchWord, page: self.page)
            .subscribe { [weak self] result in
                switch result {
                case .success(let data):
                    self?.articles.append(contentsOf: data.articles)
                    if data.articles.count != 0 {
                        self?.refreshToggle.accept(())
                        self?.page += 1
                        self?.loadStatus = .initial
                    } else {
                        self?.loadStatus = .full
                    }
                case .error(let error):
                    print(error)
                    self?.loadStatus = .initial
                }
            }.disposed(by: disposeBag)
    }

QiitaのAPIだと、一回のリクエストでは最大100件までしか取得できないので、pageって変数に先に叩いたページ保存しておいて、一回リクエエスト叩くごとに増やしていくことで順番に取得していくことができます。

ソースコードの全貌載せておきます。

github.com

参考にした記事はこちら↓

qiita.com

少なくとも私がやった感じQiitaのAPIって割とすぐに403(リクエスト送りすぎ)って返って来ちゃうのでスクロールし過ぎには注意が要りそうです、、、汗

何か気になる所ありましたら是非教えて下さい〜!!

ブログ始めます

自己紹介

はじめまして! お茶の水女子大学大学院修士1年のあおいというものです。

専攻は情報ではなく数学ですが、勉強するに連れて数学って実際どこに活きてるの?数学だけやってて将来大丈夫なの?っていう不安を持つように。

いろいろ考えた結果、数学はITの世界で活きているに違いないという考えに至り(安直)インターンでプログラミングをはじめました。

やってみると開発が楽しすぎたので、現在エンジニア目指して就活してます。

このブログについて

  • 技術的なことに関する備忘録
  • インターンとか就活に関するメモ
  • その他独り言

といった感じで書いていこうと思います。

ちなみにこのブログを書こうというきっかけをくれたのは、私の就活に関する唐突な質問にめちゃ優しく答えてくれたややさんでした↓
ありがとうございます!

blog.yayawatanabe.net

まだまだ開発期間が短いので初歩的なこと、わからないことが多いと思いますが、
何卒よろしくお願いします!