面白きことは良きことなり

拙く未熟なiOSエンジニアの備忘録と戯言

NSPredicateを使うと@を全角半角区別してくれない

emailのvalidationメソッド

業務中、emailのvalidationメソッドをいじることになって知った話

下記のようなコードでemailのvalidationチェックを正規表現を行なっていたのだが、 @ が全角でも true が返ってきてしまうことが発覚した。(emailの正規表現に関しては正確ではないので、そこは雰囲気で)

func isValidEmail(string: String) -> Bool {
    let emailReg = "[A-Za-z0-9._%+-]+[A-Za-z0-9]+@[A-Za-z0-9]+[A-Za-z0-9.-]+\.[A-Za-z]{2,6}"
    let predicateMail = NSPredicate(format:"SELF MATCHES %@", emailReg)
    return predicateMail.evaluate(with: string)
}

Androidでは全角半角区別されていた

同じプロジェクトのAndroid側では似た処理を行なっていたが、 @ が半角の時のみvalidとして扱われていたので、その振る舞いに疑問を覚え、Discord上のios-developers-japanコミュニティに質問投げた。

f:id:aryzae:20171213001954p:plain

NSPredicate 使ってることもあって、iOSでもmacOSでももはや @ は全角半角区別されない仕様なのかと思っていた。

SwiftならNSRegularExpressionがある

そもそも NSPredicateを前任が使って書いていたり、自分がiOS上で正規表現を使ったことなくて知識が乏しく気づけなかったのだが、NSRegularExpressionクラスというのがあるのを知らなかった。(見たことある気もするけど、覚えてない)

んでこいつを使えば、下記のように判定はできるということを教えてもらった。

// loveeさんから提示のコード
let stringA = "123@456"
let stringB = "123@456"

let rangeA = stringA.range(of: "@[0-9]", options: .regularExpression, range: nil, locale: nil) //.some
let rangeB = stringB.range(of: "@[0-9]", options: .regularExpression, range: nil, locale: nil) //nil
// omochimetaruさんから提示のコード
func isValidEmailFormat2(string: String) -> Bool {
    let emailReg = "[a-zA-Z0-9._%+-/:~$^()&!']+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"
    
    // valid regexp
    let regexp = try! NSRegularExpression.init(pattern: emailReg, options: [])
    
    let nsString = string as NSString
    let matchRet = regexp.firstMatch(in: string, options: [], range: NSRange.init(location: 0, length: nsString.length))
    return matchRet != nil
}

また、NSPredicateよりNSRegularExpressionの方が標準的だろうというお話も出た。

最終的には…

NSRegularExpressionを使えば、@の全角半角の判別を正常にできるのがわかったわけだが、諸事情(時間的と引き継いだコードで振る舞いを変えづらい)により、文字列がascii文字かの判定を別途足し方式でvalidationを行なった。

var email1 = "hoge@gmail.com"
email1.canBeConverted(to: .ascii)   // true

var email2 = "hoge@gmail.com"
email2.canBeConverted(to: .ascii)   // false

Akiba.swift 忘年会のAKIBA枠でLTしてきた

Akiba.swiftとは

岩本町にあるクラスメソッドのオフィスで開催される勉強会です。

classmethod.connpass.com

主催者は、ダンボー田中さんloveeさん

オフィスの広さの関係もあって、最大でも20人ぐらいの小規模勉強会で、名前からも察せるようにアキバ系が好きな人が多く集まります。

AKIBA枠

今回の忘年会から思いつきで追加されたアキバ系のネタ用LT枠

過去3回くらいオーディエンス参加したり、技術系のLTやったりと参加してきたので、今回も賑やかしに参加した。

発表内容

speakerdeck.com

Akiba.swift + AKIBA枠 + 技術者 = bpS(バトルプログラマーシラセ)
このネタをやるしかない!マイナーなお気に入りアニメの布教もできる!と思いつきで登壇

2箇所くらいは動画を差し込んで 時間稼ぎ 発表に面白みを付加していたけど、Speakerdeckにあげるために静止画へと置き換わっている。

今後

色々iOSやSwiftから離れたことをプライベートでちまちまやってたけど、それが落ち着いたのもあって今週あたりからSwift触ってやりたいこと試したいことを行なっていくので、知見をまたどこかのLTでアウトプットしていきたいところ。

最低限このブログ上でまとめることはやる。

iPhone Xで出来ないこと2点

iPhone Xを触ってて気づいたこと

iPhone Xを私物として購入し、触ったりコードを動かしていて疑問に思ったことを調べた結果、以下の2つが判明 * network indicatorが表示されない * 逆さまの縦持ち(portrait upside down)が存在しない

network indicatorが表示されない

これは割とiPhone Xが発売された最初のほうから言われているので有名。iOS11.2の現在でも表示されないままである。

UIApplication.shared.isNetworlIndicatorVisible = true を行うことで、従来ではステータスバーにindicatorを表示でき、通信していることを示せていた。

また、場合によっては、通信時にこの表示を行わないとリジェクトされることもあるという話を見た。

しかし、なぜかiPhone Xでは表示されなくなっている。ステータスバー領域がフロントカメラで狭くなったことも影響しているのだろうが、2017年9月の新機種発表の時だったか忘れたが、iPhone Xのステータスバー上で新しいデザインのnetwork indicatorが表示されているシーンを見た記憶がある。

iPhone X自体前倒しでリリースされたと言われているので、今後のiOSのアップデートで表示されるようになるのでは?と思っている。

逆さまの縦持ち(portrait upside down)が存在しない

前項のnetwork indicatorが実はフロントカメラの下で見えない可能性が微レ存か?と思い、上下逆さまの画面で確認したらわかるやーんと、お試しプロジェクトを作って発覚。

iPhone XはportraitUpsideDownは機能しない。

Apple Developer Forumsでは、Appleの人曰く、"It is by design. We're getting documentation updated in a future release to reflect that." forums.developer.apple.com

つまり、デザイン上そうなってるし、ドキュメントにもそのうち反映されるとのこと。

もともと、portraitUpsideDownなんて使い道なかったし、使ってるアプリ自体極めて稀なので、なくても困らないんだろうが…

おまけの備忘録

network indicator周りで他何か使ってハックしてでも表示できないかと思い、プライベートAPIとかの表示の仕方を調べた。割と検索しにくく、記事も古いため埋もれる可能性を考慮して残しておく

memo.sugyan.com

Apple Scriptで"scrapy crawl hoge"をshellで実行しようとして躓いた話

Terminalでは上手くいくのに、AppleScriptではエラー

Terminalを起動して、 do shell script "scrapy crawl hoge" を打つと問題なく動作するのに、Apple Scriptで do shell script "scrapy crawl hoge" のようにshellを実行しようとしたところ、 sh: scrapy: command not found とエラーが出てしまった。

PATHを通しているからTerminalでは正常に動いてるみたいだし、AppleScriptではPATHとか考慮されないぽい?

commandがなきゃ大本を見つけて叩く

scrapyが見つからないなら、直接commandを叩いてやろうと which scrapy でpathを調べ /Library/Frameworks/Python.framework/Versions/2.7/bin/scrapy だと判明する。

で、
do shell script "/Library/Frameworks/Python.framework/Versions/2.7/bin/scrapy crawl hoge" を実行するのだけれど、別のエラーが出てきた。

Scrapy 1.4.0 - no active project

Unknown command: crawl

Use "scrapy" to see available commands

大きな勘違いをしていた

scrapyは見つかったけど、今度はその引数が上手く渡せていないのか?と思って、引数の渡し方を必死に調べていた。 実際は、有効なspider nameが見つからない時のエラーが出ていたわけなのだが…。

Terminalでcommandを打っている時は、現在のpathを基点に打つので無意識的にApple Scriptも出力したファイルを実行するpathを基点に動いていると勘違いをしていた。

そこで、検証として下記のように順にshellを実行したところ…

-- "--"は、Apple Scriptでコメント
log (do shell script "pwd")                -- "/"
log (do shell script "cd /Users; pwd")     -- "/Users"
log (do shell script "pwd")                -- "/"

shellの実行は必ずrootを基点として始まってて、 do shell script "" 1行毎にrootを基点とすることが判明…。

なので、scrapy project内で scrapy crawl hoge を実行しないといけないのに、ずっとrootで実行しようとしていたわけだ。そりゃエラーにもなる。

思い込み怖い

最終的に do shell script "cd /Users/(Scrapy Project Path)/; /Library/Frameworks/Python.framework/Versions/2.7/bin/scrapy crawl hoge"

のように1行でpath移動とscrapyのcommandを書くことで正常にshell scriptが動くApple Scriptを作成できた。

もっと綺麗なやりかたもあるのかもしれないが、Apple Scriptは独特すぎて学習するのも面倒なので、必要ない限りは今回のようなやり方でいいかなと思ってる。

Xib(Nib)上で設定したfontの種類やサイズが適用されなくて困った話

遭遇した問題

とあるプロジェクトでXib上に配置したUILabelに対して、Xib上からfontの種類とサイズを変更した。
しかし実機上で確認しても、そのfontの変更は適用されていなかった。

原因

何が問題だったかというと、application(_ : launchOptions: ) -> BoolUILabel.appearance().font = UIFont(name: "System", size: 15.0) のようなことをしていた。

この appearance() は初めて知ったのだけど、説明は以下。

public protocol UIAppearance : NSObjectProtocol {

    /* To customize the appearance of all instances of a class, send the relevant appearance modification messages to the appearance proxy for the class. For example, to modify the bar tint color for all UINavigationBar instances:
        [[UINavigationBar appearance] setBarTintColor:myColor];
     
        Note for iOS7: On iOS7 the tintColor property has moved to UIView, and now has special inherited behavior described in UIView.h.
        This inherited behavior can conflict with the appearance proxy, and therefore tintColor is now disallowed with the appearance proxy.
      */
    public static func appearance() -> Self

...

appearance()で生成したclassのinstanceで行なった処理は、そのclassのinstance全てに対して同じ振る舞いをさせるもの。

Xibとか関係なくinstanceの設定が上書きされてしまうので、Xib上の設定は意味がなくなってしまう。

解決策

  • 可能なら appearance() 使ってるコードは削除する
  • コード設定し直す (今回で言えば、 UIFont(name:size:) を使ってfontを再設定する)

そもそも…

ダメ、ゼッタイ

  • 汎用的なUIKitのclassに appearance() は使うべきじゃない
  • appearance() 使うなら継承したclass等限定的に使われるもののみ
  • コードonlyなプロジェクトなら汎用的なUIKitのclassに使うのもありとは思う

これに関しては、Xibに限らずStoryboardでも例外なく発生するもののため、注意が必要。
自分はテスト用のプロジェクト作って調べまくったけど、再現しなくて分からず最終的に苦肉にも // 原因不明だけどXibで設定した内容が反映されないため、コードで設定 のようなコメントを残すことになった。
タスク完了した後、原因を知りフザケルナという気持ちになった。やるせない。

自分の無知故な部分もあるけど、コード書いた人は後に及ぼす影響を想像できなかったのだろうか…