文字っぽいの。

文字を書いています。写真も混ざります。

#iOSDC Japan 2023のレギュラートークで『君だけのGFMエディタを作ろう!』というタイトルで登壇してきました。

今年もオフライン・オンラインのハイブリッド開催!

今年のiOSDCも去年に引き続きオフライン・オンライン両方のハイブリッド開催でした。今年はブース出展の担当をしており、会場に行ける時間も少なかったので配信で見られるのはとても助かりました。

レギュラートーク登壇

めでたいことに今年も採択していただいたので、20分のレギュラートークをしてきました。なんとこれで6年連続登壇です!

Apple公式がcmark-gfmを利用したMarkdownパーザであるapple/swift-markdownを公開しています。今回の発表ではこのswift-markdownを利用して、

  • Markdownの入力補助をする便利バーの作り方
  • シンタックスハイライトを自分で実装する方法
  • プレビューはどうすればよいか?

について話しました。

世の中にはたくさんのMarkdownエディタアプリがありますが、どうしても「ここがもっとこうなればい良いのに」という要望が出てくるものです。例えばEmacs, Vim, VSCodeなどであれば自分でカスタマイズしてしまえばよいのですが、iOS, iPadOSアプリではそれが難しいです。しかし我々はエンジニアなので、なければ作ってしまうことが可能です。

しかし、いざ作ろうと思ってもMarkdownをどの処理すると良いのか取っ掛かりがないものです。 そこで今回の発表ではiOS, iPadOS, macOSMarkdownエディタを実装してみたい人の参考となるように資料をまとめてみました。

とにかくSourceLocation, SouceRangeを変換するのが大変で、文字コードに悩まされるのですが、おいでよ文字コード地獄の沼へ……という気持ちです。

今年のラーメン

今年のiOSDCではランチにキッチンカーがやってきており、そこでご飯を買うことができました。そのためお昼は会場で済ますことができて大変助かりました。しかし、せっかくラーメン激戦区である高田馬場まで行くのであれば、いくつか回りたい店舗があるのも事実。そこで、夕飯にラーメンを食べることにしました。

どちらの店舗も美味しかったので、高田馬場に訪れた際や来年のiOSDCでぜひ。

渡なべ

tabelog.com

博多ラーメン でぶちゃん 高田馬場本店

tabelog.com

まとめ

今年も無事に登壇することができて嬉しかったです。またブースや懇親会で、よくインターネットで見かける方や、すごい久しぶりに会う方や、登壇を拝聴させて頂いたスピーカーの方とじっくりとお話できて非常に楽しかったです。やはり対面でじっくり話せると、色々と深い話が聞けてとても楽しいですね。こればっかりはオンラインでは得られない効能だと感じます。

さらに今年は無限コーヒーが復活していてすごく嬉しかったです。お盆も過ぎて9月だというのに汗をかくほど暑い3日間。そんな中でのアイスコーヒーが非常に美味しかったです。ペットボトルの水もいっぱいあって助かった。

自分個人としては、6年連続登壇が嬉しかったのでここらで一段落したい気持ちもありつつ、来年も登壇できたらいいなぁという気持ちもあります。って毎年書いてるんだよなぁ。

それでは皆様、また来年お会いしましょう。

iOSDC Japan 2023で『君だけのGFMエディタを作ろう!』というお話をします。

今年もiOSDC Japanの季節がやってきましたね!ありがたいことに今年はレギュラートーク(20分)を採択してもらえました。 これで6年連続登壇となり、うれしみに舞い踊っております。

fortee.jp

プロポーザル内容は、

NSAttributedStringとSwiftUIのMarkdown対応により、 誰もが1度は「僕の考えた最強のMarkdownエディタ」を作る夢を見たでしょう。

エディタには、見出し・太字・リストなどの記法を簡単に入力できるボタンが欲しくなります。 その時に「正規表現で実現できそう」と考える人がいるかもしれません。 そう、過去の私です。 しかし、正規表現を用いた実装は失敗に終わります。

このセッションではその失敗をもとに、

  • 正規表現で入力補助機能の実装が困難な理由
  • apple/swift-markdownとcmark-gfm
  • SourceLocation をUITextViewで扱う方法
  • Syntax Highlightの自作方法 について話します。

このセッションを聞くことで、みなさんもオリジナルのMarkdownエディタが作れるようになります!

となっています。入力補助をする便利バーやシンタックスハイライトもついたMarkdownエディタを作るために、必要なノウハウをお話します。

9月2日(土)の16:50からTrack Cで話しますので、GitHub Flavored Markdownエディタを自作してみたい人や、 Markdownエディタ作りの難しさを知りたい人はぜひぜひお越しください。

また、今年のiOSDC Japanも去年と同様にオフライン+オンラインのハイブリッド開催となっています。 自分は現地で発表する予定なので、Ask the speakerや懇親会などで話しかけてもらえると嬉しいです。

ではみなさん、会場やインターネットでお会いしましょう!

南粤美食でお粥の会を開催した。

元町・中華街には、それはそれは豪華なお粥を楽しめる南粤美食というお店がある。TVドラマ版の孤独のグルメにも登場したお店なので、そちらで知っている人も多いだろう。

tabelog.com

さて、主題のお粥コースだが、このお粥コースは非常に難易度が高い。まず値段が高く1人15000円からになる。そして次に16人以上のグループ予約しか受け付けていない。つまり、このお粥コースを楽しみたい場合、1回の食事(お粥)に15000円出せる人間を16人集め、さらにその人数の予定を調整し、お店の予約が取れる日をおさえる必要がある。

そんなわけで非常に高難度なため、あまり期待せずに社内で募集をかけたらところ、21人集まった。変な会社である。

お粥コースの内容

まず前菜として、丸鶏の塩蒸し焼きが出てくる。これがいい感じに塩が効いていて旨い。

今日がお粥コースでなかったら、これだけで瓶ビールを何本も空けられる。 しかしこれはあくまでも前菜なため、心を落ち着かせてお粥の開始を待つ。

会場には最初からお粥が用意されている。人数が多いので鍋も2つあった。お粥というより重湯に近い。

そこに車海老がやってくる。生きているので、蓋をしないと全部飛び出していく。

そして、その海老をこうして、

こうじゃ。

そう、このお粥コースとは「大量の高級食材をお粥で茹でて食べていき、お粥にその出汁を染み込ませ、最後に出汁を吸収しまくったお粥を楽しむ」という、非常に贅沢なコースなのだ。

というわけで、お粥の出汁になるために茹でられた食材の様子たちをどうぞ。

アワビ、まずいわけがない。

ホタテは生で食べられるものをレアに茹でていただく。

イカとセンマイ。このイカが非常に美味しかった。センマイも下処理が丁寧にされていて一切の臭みがない。

名古屋コーチンは生姜が効いていて旨い。

しいたけは異常なサイズをしている。こんなん旨い。出汁も出まくる。

和牛は「ふるさと納税の宣材写真じゃん!」という声が出るほどにきれいなもの。当然旨い。

肉団子と真鱈のすり身団子、どちらも下味がちゃんとついていて、素材の味けいの今までとは路線が変わって良い。

山芋とかぼちゃ。山芋は外はホクホク、中はシャキシャキになって最高。

大粒なアサリ。熊本県産と聞いてちょっとしたザワツキが発生した。でも、こんなん旨いよな。

大根は今までの出汁を全部吸っていて、ジュワジュワに旨い。

最後は揚げパンと干し貝柱を入れて、お粥を完成させていく。

そして、完成したお粥がこちら。

このお粥に至るまでに非常に大量の食材を食べてきたため、腹はパンパンである。少なそうに見えるが、ちょうどいい。なにぶん味が強い。今までの食材がどこかにいる。味がする。つまるところ、これは味である。

感想

開催までの難易度が高くて躊躇していたが、やってみたら最高の体験を得ることができた。 なかなか難しいとは思うが、ぜひ一度行ってみてほしい。

ちなみに、南粤美食はお粥コースだけのお店ではなく、少人数でも(行列はすごいが)普通に飲食をしたり、予約してコース料理を楽しめるお店である。

ケーブルレスでWebカメラとライトを運用できるようにした。

こういったセットアップをした。

一番上にはこのライトがついている。

バッテリー内臓なので、充電ケーブルを繋ぎっぱなしにしなくても良い。

その下にはiPhoneをMagSafeで固定している。これもカメラを使う時だけくっつければ良いので、充電ケーブルはつけていない。

iPhoneMacを使っていれば連携カメラ機能で、iPhoneのカメラをMacWebカメラとして利用することが可能。

support.apple.com

これが非常に便利で、MaciPhoneを有線接続しなくても使えるという優れもの。

両方ともケーブルレスで使えるので、Macとはなにも繋げずにWebカメラとライトが運用できるようになった。

使ったもの

これらを順番に繋げてあげれば良い。

最後のクランプは設置場所によっては使えないので、ミニ三脚にしたり、ポールにすると良い。

感想

かなりコンパクトに便利な仕組みができた。ただ、MTGの時間が長い人は流石に充電ケーブルをつけないと1日は持たないと思うので、注意して欲しい。

プロポーザルが不採択になった時に。

社内で話した内容を公開できる状態にしました。 そのため、文意がブレていたり、補足が足りないかもしれません。


プロポーザルが不採択になると、どうしても悲しい気持ちになります。

試験で不合格になったような、なにか大きな失敗をしてしまったような。プロポーザルが不採択になったことが、自分のやってきたことの否定や自分自身が否定されてしまったように感じる人もいるでしょう。

"Don't take it personally." という言葉があります。コードや論文をレビューをされる時に気をつけるべきポイントとして話されることが多いです。レビューなどで受けた指摘は、あくまでも制作物の内容、表現に関するものであり、「あなた自身」に対するものではないので冷静に受け止めましょうというものです。

しかし、人間はそう簡単に気持ちを切り替えられないものです。そのため、不採択のストレスを忌避するように、

  • カンファレンスやコミュニティを下げる
    • 運営は何も分かってない、センスがないと批判する
    • 採択されてるプロポーザルが面白くないと他人を攻撃する
  • 自分の出したプロポーザルを卑下する
    • 元々面白くないと思っていたと言う
    • 本気じゃなかったと言う
    • 自分はセンス・能力が低いからと言う

といった行動をとることで自分を守ろうとします。これは人間の心の防御反応として仕方がないことなのですが、公に発言する事は、ぐっとこらえたほうが良いでしょう。

このネガティブをコミュニティに撒き散らしても、特に良い効果は生まれません。なんならコミュニティから「面倒な人」だと疎まれてしまうかもしれません。

「次こそは!」という気持ちで、日々の活動の中から発表のネタを探していけると良いですね。

WKWebViewで表示しているコンテンツのサイズ変更をKVOで検知する

課題

WKWebViewでなんらかのWebページを表示している時に、ページ全体の高さや幅などを取得したい場合がある。簡単に検索して実装をすると、

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    // ここで高さを取得する
}

このようなコードを書くことになる。しかし、Webページは画像の読み込みやiframe、JavaScriptの影響で後からサイズが変わっていくので、このコードでは正しくサイズ取得するのは困難である。

解決方法

WKWebViewはUIScrollViewを内包しており、UIScrollViewはKVOに対応しているため、これを利用する。

import UIKit
import WebKit

final class ViewController: UIViewController {
    @IBOutlet weak var webView: WKWebView!

    private var observer: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        observer = webView.scrollView.observe(\.contentSize, options: [.new], changeHandler: { scrollView, value in
            guard let newValue = value.newValue else {
                return
            }

            print(newValue.height, newValue.width)
        })

        let request = URLRequest(url: URL(string: "https://fromatom.hatenablog.com/entry/2023/03/08/100000")!)
        webView.load(request)
    }
}

これによって、WKWebViewで表示したWebページのサイズが変更が監視されるため、サイズが変わるたびに print(newValue.height, newValue.width) が呼ばれるようになる。

なお、KVOは古い書き方もあるが、Swift4から使えるようになったこのBlock-baseのものを使うのが推奨される。

`WKUserContentController.add(_:name:)` はちゃんと解放しないとメモリリークする。

開発環境

背景

WKWebViewを使っていると、JavaScriptとSwiftでやり取りをしたいことがあります。そんな時に調べていると、こういうサンプルコードを良く見ます。

final class SomeViewController: UIViewController {
    @IBOutlet weak var webView: WKWebView! {
        didSet {
            webView.configuration.userContentController.add(self, name: "EventName")
        }
    }
}

extension SomeViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "EventName" {
            // JS側で `webkit.messageHandlers.EventName.postMessage(message);` を呼ぶとここが呼ばれる。
        }
    }
}

課題

上記コードの

webView.configuration.userContentController.add(self, name: "EventName")

では self を渡していますがこの self によって循環参照が発生して、メモリリークします。そのため、SomeViewControllerの画面を閉じたとしてもメモリが解放されず、SomeViewControllerを開けば開くほどメモリを食いつぶしていきます。

解決方法1

使い終わった際にはちゃんと解放してあげましょう。

webView.configuration.userContentController.removeScriptMessageHandler(forName: "EventName")

JavaScriptとSwiftの連携が不要になったタイミングや、viewDidDisappearなどで呼んでおくと良さそうです。

解決方法2

解決方法1では、大量にJSのEvent登録をしたり、removeScriptMessageHandler を呼ぶタイミングが難しいときに扱いが困難になります。そこで、渡した self がweakになるように間に1つclassをかませてあげます。

class WKScriptMessageHandlerWithWeakReference: NSObject, WKScriptMessageHandler {
    private weak var delegate: WKScriptMessageHandler?

    init(delegate: WKScriptMessageHandler) {
        self.delegate = delegate
        super.init()
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        delegate?.userContentController(userContentController, didReceive: message)
    }
}

使う時はこんな感じ

wkWebView.configuration.userContentController.add(WKScriptMessageHandlerWithWeakReference(self), name: "EventName")

これによってわざわざ removeScriptMessageHandler を呼ばなくても良くなります。

感想

昔のNotificationCenterやKVOを思い出しました。

参考文献

WKWebViewを利用した実装でメモリリークしやすいパターン2つ - Qiita