2

よし、iOS のプライベート クラスという深淵にハッキングしすぎることと、究極の再利用可能なコードを求める聖戦との間には紙一重です。

UINavigationBar の下に、高さ約 40 ピクセルの任意の UIView が必要です。通常、これは囲まれた View に放り込むことができますが、この UIView をUINavigationBarにとどめておきたいと思います。したがって、ユーザーが別のビューにドリルダウンしても、ナビゲーション バーの下の UIView は表示されたままになります。

UINavigationController の RootViewController として配置されるコンテナー UIViewController を構築しようとしましたが、これは役に立ちません。ユーザーが新しいビューを押すと、コンテナー ビューがスライド アウトし、永続的なバーが表示されるからです。それと一緒に行きます。

UINavigationController を UIViewController 内に配置すると、必要な UIView バーをオーバーレイできますが、ビューがナビゲーション スタックからプッシュまたはポップされたときと同様に、内部ビューはまだフレームを適切に変更していないようです。ビューのアニメーションが不十分です (UINavigationController が画面のさらに下にある新しい Y 位置を認識していないため、Y 位置が変更され、画面から移動します)。

そこで、UINavigationController のサブクラス化を検討しました。「このクラスはサブクラス化を目的としていません。」と書かれていますが、常に例外があります。このサブクラスは、永続的な UIView をナビゲーション バーの下に追加し、コンテンツ セクション (通常のビューが表示される場所) のサイズを変更するだけで、すべてが機能します。

しかし、ObjC ランタイム クラスをいじってみたところ、これを機能させるために調整する魔法の変数が見つかりません (UINavigationController の _containerView はそのような変数の 1 つです)。

これを行う最善の方法は何ですか?コードをコピーして貼り付けて、このバーを表示するすべての ViewControllers にするよりも良い方法はありますか?

4

1 に答える 1

5

ナビゲーション バーの下に表示されるアップロード プログレス バーまたは類似のものを配置しようとしていると思いますが、どのビューに移動しても表示されます。私はこれを数週間前に実装しましたが、追加の注意事項がありました。IIViewDeck を使用しているため、UINavigationController 全体の変更も処理できる必要がありました。

私がしたことは、ビューをナビゲーション バーのサブビューとして追加することでした。私のナビゲーション バーとナビゲーション コントローラーは両方ともサブクラス化されています (そして、アプリは問題なく App Store に提出されています)。最初は、ナビゲーション バーをカスタマイズする必要があったため、UIAppearanceこれを行いましたが、このようなタスクにかなりの柔軟性を与えてくれるので、今では贈り物のように思えます。

  1. カスタマイズされた UINavigationController と UINavigationBar を作成する
  2. タッチを処理する必要がある場合は、実装する必要がありますhitTest:withEvent。そのコードも以下に含めました。
  3. ビューが変化するアプリUINavigationController(タブバーアプリ、またはUINavigationController中央にあるものを変更できるサイドメニューなど)がある場合、進行状況ビューが「フォロー」できるように、中央のビューコントローラーを監視するKVOを実装しましたその周り。コードも下にあります。

これは、カスタム UINavigationController と UINavigationBar を作成する方法です。

+ (ZNavigationController *)customizedNavigationController
{
 //This is just a regular UINavigationBar subclass - doesn't have to have any code but I personally override the pop/push methods to call my own flavor of viewWill/Did/Appear/Disappear methods - iOS has always been a bit finicky about what gets called when
    ZNavigationController *navController = [[ZNavigationController alloc] initWithNibName:nil bundle:nil];

    // Ensure the UINavigationBar is created so that it can be archived. If we do not access the
    // navigation bar then it will not be allocated, and thus, it will not be archived by the
    // NSKeyedArchvier.
    [navController navigationBar];

    // Archive the navigation controller.
    NSMutableData *data = [NSMutableData data];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:navController forKey:@"root"];
    [archiver finishEncoding];

    // Unarchive the navigation controller and ensure that our UINavigationBar subclass is used.
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [unarchiver setClass:[ZNavigationBar class] forClassName:@"UINavigationBar"];
    ZNavigationController *customizedNavController = [unarchiver decodeObjectForKey:@"root"];
    [unarchiver finishDecoding];

    // Modify the navigation bar to have a background image.
    ZNavigationBar *navBar = (ZNavigationBar *)[customizedNavController navigationBar];

    [navBar setTintColor:kZodioColorBlue];
    [navBar setBackgroundImage:[UIImage imageNamed:@"Home_TopBar_RoundCorner_withLogo"] forBarMetrics:UIBarMetricsDefault];

    return customizedNavController;
}

これで、独自のカスタムZNavigationBar(これらは私のコードの実際の名前です。すべてを変更するのは大変でした) で、タッチ処理を実装します。変数名は一目瞭然です。この進行状況バーの「アクセサリ ビュー」、「メイン エリア」、または「右側のアクセサリ ビュー」:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    DDLogVerbose(@"Got a touch at point X: %f Y: %f", point.x, point.y);

 //First check if the progress view is visible and touch is within that frame
    if ([[JGUploadManager sharedManager] progressViewVisible] &&
        CGRectContainsPoint([[JGUploadManager sharedManager] uploadProgressView].frame, point))
    {
 //Modified frame - have to compensate for actual position of buttons in the view from the standpoint of the UINavigationBar. Can probably use another/smarter method to do this.
        CGRect modifiedCancelButtonFrame = [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryFrame;
        modifiedCancelButtonFrame.origin.y += [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryView.superview.frame.origin.y;

 //Do the same thing for the right view
        CGRect modifiedRetryButtonFrame = [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryFrame;
        modifiedRetryButtonFrame.origin.y += [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryView.superview.frame.origin.y;


 //Touch is on the left button
        if ([[JGUploadManager sharedManager] progressViewVisible] &&
            [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryView &&
            CGRectContainsPoint(modifiedCancelButtonFrame, point))
        {
            return [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryView;
        }


 //Touch is on the right button
        else if ([[JGUploadManager sharedManager] progressViewVisible] &&
                 [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryView &&
                 CGRectContainsPoint(modifiedRetryButtonFrame, point))
        {
            return [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryView;
        }

 //Touch is in the main/center area
        else
        {
            return [[JGUploadManager sharedManager] uploadProgressView];
        }

    }

 //Touch is not in the progress view, pass to super
       else
    {
        return [super hitTest:point withEvent:event];
    }
}

これで、ナビゲーション コントローラーに「続く」KVO パーツができました。KVO をどこかに実装します。シングルトンの「アップロード マネージャー」クラスを使用し、そのクラスの init に配置しますが、それはアプリの設計次第です。

ZRootViewController *rootViewController = ((JGAppDelegate*)[UIApplication sharedApplication].delegate).rootViewDeckController;
[rootViewController addObserver:sharedManager
                     forKeyPath:@"centerController"
                        options:NSKeyValueObservingOptionNew
                        context:nil];

次に、もちろんこれを実装します。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    DDLogVerbose(@"KVO shows something changed: %@", keyPath);

    if ([keyPath isEqualToString:@"centerController"])
    {
        DDLogVerbose(@"Center controller changed to: %@", object);
        [self centerViewControllerChanged];
    }

}

そして最後にcenterViewControllerChanged関数:

- (void)centerViewControllerChanged
{
    ZRootViewController *rootViewController = ((JGAppDelegate*)[UIApplication sharedApplication].delegate).rootViewDeckController;

    ZNavigationController *centerController = (ZNavigationController *)rootViewController.centerController;

    //Check if the upload progress view is visible and/or active
    if (self.uploadProgressView.frame.origin.y > 0 && self.uploadProgressView.activityIndicatorView.isAnimating)
    {
        [centerController.navigationBar addSubview:self.uploadProgressView];
    }

    //Keep weak pointer to center controller for other stuff
    DDLogVerbose(@"Setting center view controller: %@", centerController);
    self.centerViewController = centerController;
}

私があなたの質問を誤解した場合、私は世界で最も長いSOの回答を入力しましたが、無駄でした. これがお役に立てば幸いです。説明が必要な部分があればお知らせください。

于 2013-03-27T02:42:10.110 に答える