20190822のSwiftに関する記事は8件です。

【Swift】PasoriのreadWithoutEncryptionByメソッドがreleaseビルドで動作しなかった話。

発生事象

以下のメソッドでICカードの諸々の情報を取得することができます。

(NSString *)readWithoutEncryptionBy:(felica_card_t)card

今回発生したのは、、

Debugビルドだと、値を取得することが出来るのに、、

releaseビルドだと、NULLが返却されて、読取り直後に落ちてしまうという事象です。

解決策

Screen Shot 2019-08-22 at 11.09.54 PM.png

こうしたら直った。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

Screen Shot 2019-08-22 at 11.10.04 PM.png

解説

GCC_OPTIMIZATION_LEVEL
という、コンパイル時の最適化レベルを最低レベルに設定することで、上記の事象は解決しました。
※ちなみに、その次のFastというレベルでもNGでした。

参考文献

iOSプロジェクトのBuildを高速化する
Apple 公式ドキュメント

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

[Swift 5]CodableでJSON内の日付データをDate型に変換するときの拡張。ミリ秒付きISO8601など

Codable(Decodable)とJSONDecoderを使ってJSONファイルを構造体(Struct)に変換するとき、

{
  "updatedAt":"2019-08-14T04:04:30+09:00",
}

といった文字列がJSONに含まれているとします。

ResponseStructure.swift
struct ResponseStructure: Decodable {
  let updatedAt: Date
}

というstructを用意して、

JSONDecoderでデコードすれば…

SampleReposotory.swift
let decoder = JSONDecoder()
do {
    let response = try decoder.decode(ResponseStructure.self, from: data)
    print(response)

} catch let error {
    print(error)
}

きちんと自動でDate型に変換してくれます。

しかし、

{
  "updatedAt":"2019-08-14T04:04:30.000+09:00",
}

といった例えばミリ秒付きの日付には対応していないので、拡張して対応する必要があります。

DateFormatterを拡張する

DateFormatter+.swift
extension DateFormatter {
    /// ミリ秒付きのiso8601フォーマット e.g. 2019-08-22T09:30:15.000+0900
    static let iso8601Full: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

extensionでDateFormatterを拡張して、

SampleReposotory.swift
let decoder = JSONDecoder()
do {
    let response = try decoder.decode(ResponseStructure.self, from: data)
    decoder.dateDecodingStrategy = .formatted(.iso8601Full)  // 追加
    print(response)

} catch let error {
    print(error)
}

JSONDecoderのdateDecodingStrategyプロパティで.formatted(.iso8601Full)と指定すればOKです。

拡張のもう一例("20190822093015"みたいな形式)

20190822093015のような日付8桁、時間6桁の場合のフォーマット例です。

DateFormatter+.swift
extension DateFormatter {
  /// 日付8桁・時間6桁のフォーマット e.g. 20190822093015
    static let yyyyMMddHHmmss: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMddHHmmss"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

ここでいうyyyyMMddHHmmssの意味は、
yyyy = 年
MM = 月
dd = 日
HH = 時
mm = 分
ss = 秒
になります。

ソースコード

コード全文はGitHubに置いてあります。

Riscait/Extensions | GitHub

参考

https://useyourloaf.com/blog/swift-codable-with-custom-dates/

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

[Swift 5]画面を縦スクロールした時にNavigationBarを隠したり出したり(UIScrollViewDelegate)

縦スクロールされたとき、NavigationBarをスッと隠すアニメーション、かっこいいですよね。
コンテンツ表示を広くとれるのでユーザーにとっても嬉しい挙動だと思います。

UITableView等のUIScrollViewを親に持っているViewを使っている場合は以下のように書くだけで簡単に作用します。

delegateは設定してあると思いますが、確認しておきましょう。※詳しくは後述

ViewController.swift
final class ViewController: UIViewController {

    // スクロールでNavigationBarを隠す
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let isBarHidden = scrollView.panGestureRecognizer.translation(in: scrollView).y < 0
        navigationController?.setNavigationBarHidden(isBarHidden, animated: true)
    }
}

ただし、ScrollViewを使用していない、UIStackViewやUILabel、UIButtonなどだけで構成された画面の場合はもう一手間必要です。

UIScrollViewDelegateに適合する必要がありますので、以下のように書きましょう。

ViewController.swift
final class ViewController: UIViewController {
    // 省略
}

extension ViewController: UIScrollViewDelegate {
    // スクロールでNavigationBarを隠す
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let isBarHidden = scrollView.panGestureRecognizer.translation(in: scrollView).y < 0
        navigationController?.setNavigationBarHidden(isBarHidden, animated: true)
    }
}

追記:delegateの追加の必要な場合もある

例えば、単一のUITextViewで構成されたViewControllerの場合は、delegateを繋いでおく必要がありますのでご注意を。
StoryboardでUITextViewからViewControllerへcontrolボタンを押しながらドラッグし、Outletsdelegateを選べばOKです。
スクリーンショット 2019-08-23 14.12.18.png

以上です。

参考

https://qiita.com/naoyawatanabe/items/8270e8ec08f46d4d28aa

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

Swift で NSCache を ジェネリクスに(Swift5.0.1版)

Swift で NSCache を Generics にして便利に

上記の記事を参考にキャッシュのジェネリクスを書いたところ、
Swift5.0.1だと動かなかったのと、
NSCacheとインターフェイスが違うのが個人的には好きじゃなかったので、下記のように書き換えてみました。

GenericCache
//KeyTypeがT, ObjectTypeがU
class GenericCache<T, U> {

    private var cache = NSCache<AnyObject, AnyObject>()

    var delegate: NSCacheDelegate? {
        get { return self.cache.delegate }
        set { self.cache.delegate = newValue }
    }

    var name: String {
        get { return self.cache.name }
        set { self.cache.name =  newValue }
    }

    func object(forKey: T) -> U? {
        let forKey = forKey as AnyObject
        return self.cache.object(forKey: forKey) as? U
    }

    func setObject(_ obj: U, forKey key: T) {
        let obj = obj as AnyObject
        let key = key as AnyObject
        self.cache.setObject(obj, forKey: key)
    }

    func setObject(_ obj: U, forKey key: T, cost g: Int) {
        let obj = obj as AnyObject
        let key = key as AnyObject
        self.cache.setObject(obj, forKey: key, cost: g)
    }

    func removeObject(forKey: AnyObject) {
        self.cache.removeObject(forKey: forKey)
    }

    func removeAllObjects() {
        self.cache.removeAllObjects()
    }

    var totalCostLimit: Int {
        get { return self.cache.totalCostLimit }
        set { self.cache.totalCostLimit = newValue }
    }

    var countLimit: Int {
        get { return self.cache.countLimit }
        set { self.cache.countLimit = newValue }
    }

    var evictsObjectsWithDiscardedContent: Bool {
        get { return self.cache.evictsObjectsWithDiscardedContent }
        set { self.cache.evictsObjectsWithDiscardedContent = newValue }
    }
}
呼び出し例
//インスタンス化
let cache = GenericCache<String, UIImage>()
cache.countLimit = 1000

//キャッシュする処理
let key = "cat"
let image = UIImage(url: url) //そういうurlがあるとして
cache.setObject(image, forKey: key)

//取り出し
let catImage = cache.object(forKey: key) //UIImage?が返る

本で読んで知識としては知っていたもののなかなか実装に使えなかった、
ジェネリクスをはじめて使えたので嬉しかったです。

あと
「All paths through this function will call itself」というワーニングが、
delegateのGetterとevictsObjectsWithDiscardedContentのSetterで出ます。
かといってGetterとSetterを定義しないと怒られるので、仕方ないですね。
⇒2019/08/23追記
 人から指摘されて気づきましたが、self「.cache」が抜けていました。。。
 今は修正しています

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

それまで実行できてたのに突然AppDelegateでsignal SIGBRT(& terminaing... NSException... call in stack)[Swift][実機]

概要

今まで実行できてたプロジェクトが再起動とかした途端
AppDelegatesignal SIGBRT

terminaing... NSException... call in stack

が出てしまってクラッシュになってしまった
ググって出てきた
・xcodeを再起動
・Build FolderをClean
・Derived Dataを削除
をすべてやったもののうまくいかず

参考

IBDesignableのRending Errorで困り果てたので状況別の解決策[Swift] - Qiita

対策

思いつきで実機でインストールされてたプロジェクトのアプリを消してもう一度やってみたところ実行できた

原因

詳細はわからんのですが
再起動してから実行したプロジェクトはバージョンがそれまでと変わっており
・実機側ではそれまでとは別で新しくアプリがインストールされていた
・xcodeのバグでうまくいかなかった状態のものが実機側に残っている
#概要でやった対策が実機のアプリ側で反映されず、ただ起動し直した感じになってしまった?

というようなことが原因かなと考えました
もしわかる方いればご意見ください

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

突然AppDelegateでsignal SIGBRT(& terminating with uncaught exception of type NSException & First throw call stack)[Swift][実機]

概要

今まで実行できてたプロジェクトが再起動とかした途端
AppDelegatesignal SIGBRT

*** First throw call stack:
...
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

が出てしまってクラッシュになってしまった
ググって出てきた
・xcodeを再起動
・Build FolderをClean
・Derived Dataを削除
をすべてやったもののうまくいかず

参考

IBDesignableのRending Errorで困り果てたので状況別の解決策[Swift] - Qiita

対策

思いつきで実機でインストールされてたプロジェクトのアプリを消してもう一度やってみたところ実行できた

原因

詳細はわからんのですが
再起動してから実行したプロジェクトはバージョンがそれまでと変わっており
・実機側ではそれまでとは別で新しくアプリがインストールされていた
・xcodeのバグでうまくいかなかった状態のものが実機側に残っている
#概要でやった対策が実機のアプリ側で反映されず、ただ起動し直した感じになってしまった?

というようなことが原因かなと考えました
もしわかる方いればご意見ください

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

NSTextAttachmentで追加したイメージの色を変える時にハマった

UILabelにイメージを表示して色を変える記事は星の数ほどある。

大体、以下のような感じで実現できる。
* UIImage を.withRenderingMode(.alwaysTemplate) して生成
* NSTextAttachmentのimageにセットする
* NSAttributedString(attachment:) をNSMutableAttributedStringにappendする
* NSMutableAttributedStringにaddAttribute(_,value:range:)してNSAttributedString.Key.foregroundColorをキーとしてUIColorをセットする

こんなの

        let image = UIImage(named: "foo")!
        let attributedString = NSMutableAttributedString(string:"")
        let templateImage = image.withRenderingMode(.alwaysTemplate)

        let textAttachment = NSTextAttachment()
        textAttachment.image = templateImage
        textAttachment.bounds = CGRect(x: 0, y: 0, width: 20, height: 20)

        let attrString = NSAttributedString(attachment: textAttachment)
        attributedString.append(attrString)

        attributedString.addAttribute(
            NSAttributedString.Key.foregroundColor,
            value: UIColor.green,
            range: NSRange(location: 0, length: attributedString.length))

        label.attributedText = attributedString

しかし、もう何度も書いてるはずのこのコードに2時間くらいハマった。

  • Xcode10.1
  • Swift4.2

問題

セットしたUIColorが全く反映されない。

原因

NSMutableAttributedStringの先頭が文字列の場合は問題ないのだが、
なんと先頭がNSTextAttachmentから始まる場合にaddAttributeしてもイメージにUIColorが適用されないということがわかった。

NSMutableAttributedStringの内容が
"?text"だと問題あり、
"text?"だと問題ない。

問題あり

failure.png

問題なし

success.png

対策

NSMutableAttributedStringの先頭に文字列(スペース)を入れて回避する。

        let image = UIImage(named: "foo")!
        let attributedString = NSMutableAttributedString(string:"")  // <- ここに入れてもOK
        let templateImage = image.withRenderingMode(.alwaysTemplate)

        let textAttachment = NSTextAttachment()
        textAttachment.image = templateImage
        textAttachment.bounds = CGRect(x: 0, y: 0, width: 20, height: 20)

        let attrString = NSAttributedString(attachment: textAttachment)
        attributedString.append(attrString)

        attributedString.insert(NSAttributedString(string: " "), at: 0)  // <- これ

        attributedString.addAttribute(
            NSAttributedString.Key.foregroundColor,
            value: UIColor.green,
            range: NSRange(location: 0, length: attributedString.length))

        label.attributedText = attributedString

これで一件落着、と思いきやこれでもまだ罠がある。

先頭に文字列を入れることで先頭の文字列分後退するし、イメージのみの場合と比べてbaselineが少し上になる。
密にレイアウトしているアプリだと上が切れたり下に変なスペースが生まれたりするし、
大体、 コード的に非常に気持ち悪い。

先頭がイメージ

failure.png

先頭が文字列

問題は回避されるが、先頭にスペース(文字列)ができるのとbaselineが微妙に上に。
success.png

対策の対策

頑張ってbaselineを合わせたとしても先頭にスペースは頂けない。
またAttributedStringの処理に依存してレイアウトするのも後々痛い目を見そう。
ちょっと泥臭いがイメージ自体に色をオーバレイさせる方法にした。

class HogeView {
    func fuga() {
        let image = UIImage(named: "foo")!
        let attributedString = NSMutableAttributedString(string:"")
        let templateImage = image.withRenderingMode(.alwaysTemplate)

        let textAttachment = NSTextAttachment()
        textAttachment.image = templateImage.overray(with: .red)
        textAttachment.bounds = CGRect(x: 0, y: 0, width: 20, height: 20)

        let attrString = NSAttributedString(attachment: textAttachment)
        attributedString.append(attrString)

        label.attributedText = attributedString
    }
}

extension UIImage {
     func overray(with fillColor: UIColor) -> UIImage {

          // using scale correctly preserves retina images
         UIGraphicsBeginImageContextWithOptions(size, false, scale)
         let context: CGContext! = UIGraphicsGetCurrentContext()
         assert(context != nil)

          // correctly rotate image
         context.translateBy(x: 0, y: size.height)
         context.scaleBy(x: 1.0, y: -1.0)

          let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)

          // draw tint color
         context.setBlendMode(.normal)
         fillColor.setFill()
         context.fill(rect)

          // mask by alpha values of original image
         context.setBlendMode(.destinationIn)
         context.draw(self.cgImage!, in: rect)

          let image = UIGraphicsGetImageFromCurrentImageContext()
         UIGraphicsEndImageContext()
         return image!
     }
 }

結果

solved.png

感想

文章の先頭にアイコンとか何らかのイメージ置いたりするケースは結構ありそうなもんだが。。

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

RxSwift+MVVMでベース実装

公開にあたって

RxSwift,MVVMについて絶賛学習中なので、是非意見をいただきたいです。
とりあえず、githubの公開のみ。
随時、ソースの説明等更新を追記していきます。

githubはこちら

RxSwift+MVVVベース実装

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