11

UINavigationBar私が克服しようとしている小さな問題があります。ビュー コントローラーでメソッドを使用してステータス バーを非表示にprefersStatusBarHidden()すると (アプリ全体のステータス バーを無効にしたくありません)、ナビゲーション バーは、ステータス バーに属する高さの 20pt を失います。基本的にナビゲーションバーは縮小します。

ここに画像の説明を入力 ここに画像の説明を入力

さまざまな回避策を試していましたが、それぞれに欠点があることがわかりました。次に、メソッドのスウィズリングを使用して、この問題を解決するクラスに呼び出されるプロパティを追加するこのカテゴリに出会いましたObjective-C でテストしたところ、動作しました。元の Objective-C コードのヘッダー実装を次に示します。fixedHeightWhenStatusBarHiddenUINavigationBar

今は Swift でアプリを作成しているので、Swift に翻訳してみました。

私が最初に直面した問題は、Swift 拡張機能がプロパティを格納できないことでした。fixedHeightWhenStatusBarHiddenそのため、値を設定できるようにするプロパティを宣言するには、計算されたプロパティで解決する必要がありました。しかし、これは別の問題を引き起こします。どうやら、計算されたプロパティに値を割り当てることはできません。そのようです。

self.navigationController?.navigationBar.fixedHeightWhenStatusBarHidden = true

エラーCannot assign to the result of this expression が表示されます。

とにかく以下は私のコードです。エラーなしでコンパイルできますが、動作しません。

import Foundation
import UIKit

extension UINavigationBar {

    var fixedHeightWhenStatusBarHidden: Bool {
        return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue
    }

    func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize {

        if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden {
            let newSize = CGSizeMake(self.frame.size.width, 64)
            return newSize
        } else {
            return sizeThatFits_FixedHeightWhenStatusBarHidden(size)
        }

    }
    /*
    func fixedHeightWhenStatusBarHidden() -> Bool {
        return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue
    }
    */

    func setFixedHeightWhenStatusBarHidden(fixedHeightWhenStatusBarHidden: Bool) {
        objc_setAssociatedObject(self, "FixedNavigationBarSize", NSNumber(bool: fixedHeightWhenStatusBarHidden), UInt(OBJC_ASSOCIATION_RETAIN))
    }

    override public class func load() {
        method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
    }

}

fixedHeightWhenStatusBarHidden()途中のメソッドはコメントアウトされています。これを残すと、メソッドの再宣言エラーが発生するためです。

これまで Swift や Objective-C でメソッドの入れ替えを行ったことがないため、この問題を解決するための次のステップがわからないか、まったく可能でさえありません。

誰かがこれに光を当てることができますか?

ありがとうございました。


更新 1: newacct のおかげで、プロパティに関する最初の問題が解決されました。しかし、コードはまだ機能しません。load()エクステンションで実行がメソッドに到達していないことがわかりました。この回答のコメントから、あなたのクラスは の子孫でNSObjectある必要があることを学びました。ですから、なぜこれがまだ機能していないのかわかりません。もう 1 つのオプションは追加ですが、Swift では拡張機能に追加することはできません。以下は、更新されたコードです。UINavigationBarNSObjectNSObjectProtocol@objc

問題はまだ未解決です。

import Foundation
import UIKit

let FixedNavigationBarSize = "FixedNavigationBarSize";

extension UINavigationBar {

    var fixedHeightWhenStatusBarHidden: Bool {
        get {
            return objc_getAssociatedObject(self, FixedNavigationBarSize).boolValue
        }
        set(newValue) {
            objc_setAssociatedObject(self, FixedNavigationBarSize, NSNumber(bool: newValue), UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize {

        if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden {
            let newSize = CGSizeMake(self.frame.size.width, 64)
            return newSize
        } else {
            return sizeThatFits_FixedHeightWhenStatusBarHidden(size)
        }

    }

    override public class func load() {
        method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
    }

}

更新 2: Jasper は目的の機能を実現するのに役立ちましたが、明らかにいくつかの大きな欠点があります。

load()ジャスパーが示唆したように、拡張機能のメソッドが起動していなかったため、次のコード ブロックをアプリ デリゲートのdidFinishLaunchingWithOptionsメソッドに移動しました。

method_exchangeImplementations(class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits:"), class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits_FixedHeightWhenStatusBarHidden:"))

また、プロパティのゲッターで戻り値を「true」にハードコーディングするfixedHeightWhenStatusBarHidden必要がありました。アプリのデリゲートでスウィズリング コードが実行されるようになったため、View Controller から値を設定することはできません。これにより、再利用性に関しては拡張機能が冗長になります。

したがって、問題はまだ多かれ少なかれ未解決です。誰かがそれを改善するアイデアを持っているなら、答えてください。

4

5 に答える 5

2

これはあなたの Swift の質問に対する直接的な回答ではありませんが ( s のloadSwift 拡張機能では機能しないバグのようですNSObject)、ただし、元の問題に対する解決策があります

UINavigationBar をサブクラス化し、sizzled ソリューションに同様の実装を提供すると、iOS 7 および 8 で機能することがわかりました。

class MyNavigationBar: UINavigationBar {
    override func sizeThatFits(size: CGSize) -> CGSize {
        if UIApplication.sharedApplication().statusBarHidden {
            return CGSize(width: frame.size.width, height: 64)
        } else {
            return super.sizeThatFits(size)
        }
    }
}

次に、ストーリーボードでナビゲーション バーのクラスを更新するかinit(navigationBarClass: AnyClass!, toolbarClass: AnyClass!)、新しいクラスを使用するためにナビゲーション コントローラーを作成するときに使用します。

于 2014-10-29T22:03:57.733 に答える
1

Swift は、拡張機能に load() クラス メソッドを実装していません。ランタイムがクラスとカテゴリごとに +load を呼び出す ObjC とは異なり、Swift では、ランタイムはクラスで定義された load() クラス メソッドのみを呼び出します。Swift 1.1 は、拡張機能で定義された load() クラス メソッドを無視します。ObjC では、各カテゴリが +load をオーバーライドして、そのカテゴリ/クラスがロードされたときに、各クラス/カテゴリが通知を登録したり、その他の初期化 (スウィズリングなど) を実行したりできるようにすることが許容されます。

ObjC と同様に、スウィズリングを実行するために拡張機能で initialize() クラス メソッドをオーバーライドしないでください。複数の拡張機能で initialize() クラス メソッドを実装する場合、1 つの実装のみが呼び出されます。これは、アプリの起動時にランタイムがすべての実装を呼び出す +load メソッドとは異なります。

したがって、拡張機能の状態を初期化するには、アプリの起動が終了したときにアプリ デリゲートからメソッドを呼び出すか、ランタイムが +load メソッドを呼び出すときに ObjC スタブからメソッドを呼び出すことができます。各拡張機能にはロード メソッドがある可能性があるため、一意の名前を付ける必要があります。NSObject から派生した SomeClass があると仮定すると、ObjC スタブのコードは次のようになります。

SomeClass+Foo.swift ファイルで:

extension SomeClass {
    class func loadFoo {
        ...do your class initialization here...
    }
}

SomeClass+Foo.m ファイルで:

@implementation SomeClass(Foo)
    + (void)load {
        [self performSelector:@selector(loadFoo);
    }
@end
于 2015-02-16T19:50:29.130 に答える