Program Club

UINavigationController에서 탐색 모음을 숨길 때 뒤로 스 와이프하지 않음

proclub 2020. 11. 2. 20:05
반응형

UINavigationController에서 탐색 모음을 숨길 때 뒤로 스 와이프하지 않음


.NET Framework에 뷰를 포함하여 상속 된 스 와이프 팩을 좋아합니다 UINavigationController. 불행히도 나는 숨기는 방법을 찾지 못하는 것 NavigationBar같지만 여전히 터치 팬을 다시 스 와이프합니다 gesture. 사용자 지정 제스처를 작성할 수 있지만 대신 UINavigationController뒤로 스 와이프 를 사용하지 않는 gesture것이 좋습니다.

스토리 보드에서 체크를 해제하면 뒤로 스 와이프가 작동하지 않습니다.

여기에 이미지 설명 입력

또는 프로그래밍 방식으로 숨기면 동일한 시나리오입니다.

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.navigationController setNavigationBarHidden:YES animated:NO]; // and animated:YES
}

상단을 숨기고 NavigationBar여전히 스 와이프 할 수있는 방법이 없습니까?


작동하는 해킹은 interactivePopGestureRecognizer의 대리인을 다음 UINavigationControllernil같이 설정하는 것입니다.

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

그러나 어떤 상황에서는 이상한 효과를 만들 수 있습니다.


다른 방법의 문제

설정 interactivePopGestureRecognizer.delegate = nil에는 의도하지 않은 부작용이 있습니다.

설정 navigationController?.navigationBar.hidden = true은 작동하지만 탐색 모음의 변경 사항을 숨길 수는 없습니다.

마지막으로, 일반적으로 UIGestureRecognizerDelegate탐색 컨트롤러 용 모델 개체를 만드는 것이 좋습니다 . UINavigationController스택 의 컨트롤러로 설정 하면 EXC_BAD_ACCESS오류 가 발생합니다 .

전체 솔루션

먼저 다음 클래스를 프로젝트에 추가합니다.

class InteractivePopRecognizer: NSObject, UIGestureRecognizerDelegate {

    var navigationController: UINavigationController

    init(controller: UINavigationController) {
        self.navigationController = controller
    }

    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return navigationController.viewControllers.count > 1
    }

    // This is necessary because without it, subviews of your top controller can
    // cancel out your gesture recognizer on the edge.
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

그런 다음 탐색 컨트롤러 interactivePopGestureRecognizer.delegate를 새 InteractivePopRecognizer클래스 의 인스턴스로 설정합니다 .

var popRecognizer: InteractivePopRecognizer?

override func viewDidLoad() {
    super.viewDidLoad()
    setInteractiveRecognizer()
}

private func setInteractiveRecognizer() {
    guard let controller = navigationController else { return }
    popRecognizer = InteractivePopRecognizer(controller: controller)
    controller.interactivePopGestureRecognizer?.delegate = popRecognizer
}

상위 컨트롤러에 테이블, 컬렉션 또는 스크롤 뷰 하위보기가 있어도 작동하는 부작용이없는 숨겨진 탐색 모음을 즐기십시오.


제 경우에는 이상한 효과를 방지하기 위해

루트 뷰 컨트롤러

override func viewDidLoad() {
    super.viewDidLoad()

    // Enable swipe back when no navigation bar
    navigationController?.interactivePopGestureRecognizer?.delegate = self 

}


func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    if(navigationController!.viewControllers.count > 1){
        return true
    }
    return false
}

http://www.gampood.com/pop-viewcontroller-with-out-navigation-bar/


다음과 같이 UINavigationController를 하위 클래스로 만들 수 있습니다.

@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>

@end

이행:

@implementation CustomNavigationController

- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
    [super setNavigationBarHidden:hidden animated:animated];
    self.interactivePopGestureRecognizer.delegate = self;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (self.viewControllers.count > 1) {
        return YES;
    }
    return NO;
}

@end

(업데이트) Swift 4.2

대리자를 재정의하거나 nil로 설정하는 다른 게시 된 솔루션이 예기치 않은 동작을 일으킨다는 것을 발견했습니다.

제 경우에는 탐색 스택의 맨 위에 있었고 제스처를 사용하여 하나를 더 팝하려고하면 실패했지만 (예상대로) 스택으로 푸시하려는 후속 시도는 이상한 그래픽 결함을 일으키기 시작합니다. 탐색 표시 줄. 이는 델리게이트가 내비게이션 바가 숨겨져있을 때 제스처가 인식되는 것을 차단할지 여부와 그 밖의 모든 동작이 삭제되는 것 이상을 처리하는 데 사용되기 때문에 의미가 있습니다.

내 테스트 gestureRecognizer(_:, shouldReceiveTouch:)에서 탐색 모음이 숨겨 졌을 때 제스처가 인식되지 않도록 차단하기 위해 원래 대리자가 구현하는 방법 인 것으로 보입니다 gestureRecognizerShouldBegin(_:). gestureRecognizerShouldBegin(_:)델리게이트 작업 을 구현하는 다른 솔루션은 구현 이 없기 때문에 gestureRecognizer(_:, shouldReceiveTouch:)모든 터치를받는 기본 동작이 발생 하기 때문 입니다.

@Nathan Perry의 솔루션이 가까워 지지만를 구현하지 않으면 respondsToSelector(_:)델리게이트에 메시지를 보내는 UIKit 코드는 다른 델리게이트 메서드에 대한 구현이 없다고 믿고 forwardingTargetForSelector(_:)호출되지 않습니다.

따라서 동작을 수정하려는 특정 시나리오에서`gestureRecognizer (_ :, shouldReceiveTouch :)를 제어하고, 그렇지 않으면 나머지 모든 항목을 델리게이트에 전달합니다.

import Foundation

class AlwaysPoppableNavigationController: UINavigationController {
    private let alwaysPoppableDelegate = AlwaysPoppableDelegate()

    override func viewDidLoad() {
        super.viewDidLoad()
        alwaysPoppableDelegate.originalDelegate = interactivePopGestureRecognizer?.delegate
        alwaysPoppableDelegate.navigationController = self
        interactivePopGestureRecognizer?.delegate = alwaysPoppableDelegate
    }
}

final class AlwaysPoppableDelegate: NSObject, UIGestureRecognizerDelegate {
    weak var navigationController: UINavigationController?
    weak var originalDelegate: UIGestureRecognizerDelegate?

    override func responds(to aSelector: Selector!) -> Bool {
        if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) {
            return true
        } else if let responds = originalDelegate?.responds(to: aSelector) {
            return responds
        } else {
            return false
        }
    }

    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return originalDelegate
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let nav = navigationController, nav.isNavigationBarHidden, nav.viewControllers.count > 1 {
            return true
        } else if let result = originalDelegate?.gestureRecognizer?(gestureRecognizer, shouldReceive: touch) {
            return result
        } else {
            return false
        }
    }
}

Hunter Maximillion Monk의 답변을 바탕 으로 UINavigationController에 대한 하위 클래스를 만든 다음 스토리 보드에서 UINavigationController에 대한 사용자 지정 클래스를 설정했습니다. 두 클래스의 최종 코드는 다음과 같습니다.

InteractivePopRecognizer :

class InteractivePopRecognizer: NSObject {

    // MARK: - Properties

    fileprivate weak var navigationController: UINavigationController?

    // MARK: - Init

    init(controller: UINavigationController) {
        self.navigationController = controller

        super.init()

        self.navigationController?.interactivePopGestureRecognizer?.delegate = self
    }
}

extension InteractivePopRecognizer: UIGestureRecognizerDelegate {
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return (navigationController?.viewControllers.count ?? 0) > 1
    }

    // This is necessary because without it, subviews of your top controller can cancel out your gesture recognizer on the edge.
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

HiddenNavBarNavigationController :

class HiddenNavBarNavigationController: UINavigationController {

    // MARK: - Properties

    private var popRecognizer: InteractivePopRecognizer?

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        setupPopRecognizer()
    }

    // MARK: - Setup

    private func setupPopRecognizer() {
        popRecognizer = InteractivePopRecognizer(controller: self)
    }
}

스토리 보드 :

스토리 보드 탐색 컨트롤러 사용자 정의 클래스


@ChrisVasseli에서 제공하는 솔루션이 최고인 것 같습니다. 질문은 Objective-C에 관한 것이므로 Objective-C에서 동일한 솔루션을 제공하고 싶습니다 (태그 참조)

@interface InteractivePopGestureDelegate : NSObject <UIGestureRecognizerDelegate>

@property (nonatomic, weak) UINavigationController *navigationController;
@property (nonatomic, weak) id<UIGestureRecognizerDelegate> originalDelegate;

@end

@implementation InteractivePopGestureDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if (self.navigationController.navigationBarHidden && self.navigationController.viewControllers.count > 1) {
        return YES;
    } else {
        return [self.originalDelegate gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch];
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if (aSelector == @selector(gestureRecognizer:shouldReceiveTouch:)) {
        return YES;
    } else {
        return [self.originalDelegate respondsToSelector:aSelector];
    }
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.originalDelegate;
}

@end

@interface NavigationController ()

@property (nonatomic) InteractivePopGestureDelegate *interactivePopGestureDelegate;

@end

@implementation NavigationController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.interactivePopGestureDelegate = [InteractivePopGestureDelegate new];
    self.interactivePopGestureDelegate.navigationController = self;
    self.interactivePopGestureDelegate.originalDelegate = self.interactivePopGestureRecognizer.delegate;
    self.interactivePopGestureRecognizer.delegate = self.interactivePopGestureDelegate;
}

@end

내 해결책은 UINavigationController클래스 를 직접 확장하는 것 입니다.

import UIKit

extension UINavigationController: UIGestureRecognizerDelegate {

    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        self.interactivePopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return self.viewControllers.count > 1
    }

}

이렇게하면 모든 내비게이션 컨트롤러가 슬라이딩으로 해제됩니다.


프록시 대리인과 함께 할 수 있습니다. 탐색 컨트롤러를 만들 때 기존 대리자를 잡습니다. 그리고 그것을 프록시에 전달하십시오. 다음을 제외하고 기존의 위임에 대한 모든 대리자 메서드를 통과 gestureRecognizer:shouldReceiveTouch:하여forwardingTargetForSelector:

설정:

let vc = UIViewController(nibName: nil, bundle: nil)
let navVC = UINavigationController(rootViewController: vc)
let bridgingDelegate = ProxyDelegate()
bridgingDelegate.existingDelegate = navVC.interactivePopGestureRecognizer?.delegate
navVC.interactivePopGestureRecognizer?.delegate = bridgingDelegate

대리 대리인 :

class ProxyDelegate: NSObject, UIGestureRecognizerDelegate {
    var existingDelegate: UIGestureRecognizerDelegate? = nil

    override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
        return existingDelegate
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
        return true
    }  
}

간단하고 부작용이없는 답변

여기에있는 대부분의 답변은 좋지만 의도하지 않은 부작용 (앱 중단)이 있거나 장황합니다.

제가 생각해 낼 수있는 가장 간단하면서도 기능적인 솔루션은 다음과 같습니다.

navigationBar를 숨기고있는 ViewController에서,

class MyNoNavBarViewController: UIViewController {

    // needed for reference when leaving this view controller
    var initialInteractivePopGestureRecognizerDelegate: UIGestureRecognizerDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()

        // we will need a reference to the initial delegate so that when we push or pop.. 
        // ..this view controller we can appropriately assign back the original delegate
        initialInteractivePopGestureRecognizerDelegate = self.navigationController?.interactivePopGestureRecognizer?.delegate
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)

        // we must set the delegate to nil whether we are popping or pushing to..
        // ..this view controller, thus we set it in viewWillAppear()
        self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(true)

        // and every time we leave this view controller we must set the delegate back..
        // ..to what it was originally
        self.navigationController?.interactivePopGestureRecognizer?.delegate = initialInteractivePopGestureRecognizerDelegate
    }
}

다른 답변은 단순히 델리게이트를 nil로 설정하는 것을 제안했습니다. 탐색 스택에서 초기보기 컨트롤러로 뒤로 스 와이프하면 모든 제스처가 비활성화됩니다. 아마도 UIKit / UIGesture 개발자에 대한 일종의 감독입니다.

또한 여기에 구현 한 일부 답변은 비표준 애플 탐색 동작을 초래했습니다 (특히 뒤로 스 와이프하면서 위아래로 스크롤 할 수있는 기능을 허용 함). 이 답변은 또한 약간 장황하고 경우에 따라 불완전한 것처럼 보입니다.


Xamarin 답변 :

IUIGestureRecognizerDelegateViewController의 클래스 정의에서 인터페이스를 구현하십시오 .

public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate

ViewController에서 다음 메소드를 추가하십시오.

[Export("gestureRecognizerShouldBegin:")]
public bool ShouldBegin(UIGestureRecognizer recognizer) {
  if (recognizer is UIScreenEdgePanGestureRecognizer && 
      NavigationController.ViewControllers.Length == 1) {
    return false;
  }
  return true;
}

ViewController ViewDidLoad()에 다음 줄을 추가하십시오.

NavigationController.InteractivePopGestureRecognizer.Delegate = this;

나는 이것을 시도했고 완벽하게 작동합니다 : 슬라이드 백 기능을 잃지 않고 탐색 모음을 숨기는 방법

아이디어는 .h에 "UIGestureRecognizerDelegate"를 구현하고이를 .m 파일에 추가하는 것입니다.

- (void)viewWillAppear:(BOOL)animated {
// hide nav bar
[[self navigationController] setNavigationBarHidden:YES animated:YES];

// enable slide-back
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
  }
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
   return YES;  
}

내 해결책은 다음과 같습니다. 탐색 모음에서 알파를 변경하고 있지만 탐색 모음이 숨겨져 있지 않습니다. 모든 뷰 컨트롤러는 BaseViewController의 하위 클래스이며 여기에 다음이 있습니다.

    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    navigationController?.navigationBar.alpha = 0.0
}

UINavigationController를 하위 클래스로 만들고 해당 메서드를 거기에 둘 수도 있습니다.


일부 사람들대신 setNavigationBarHiddenanimated로 메서드를 호출하여 성공했습니다 YES.


내비게이션 바가없는 뷰 컨트롤러에서

open override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)

  CATransaction.begin()
  UIView.animate(withDuration: 0.25, animations: { [weak self] in
    self?.navigationController?.navigationBar.alpha = 0.01
  })
  CATransaction.commit()
}

open override func viewWillDisappear(_ animated: Bool) {
  super.viewWillDisappear(animated)
  CATransaction.begin()
  UIView.animate(withDuration: 0.25, animations: { [weak self] in
    self?.navigationController?.navigationBar.alpha = 1.0
  })
  CATransaction.commit()
}

대화 형 해고 중에 뒤로 버튼이 빛나기 때문에 숨겼습니다.


내가 시도하고 완벽하게 작동하는 정말 간단한 솔루션이 있습니다. 이것은 Xamarin.iOS에 있지만 네이티브에도 적용 할 수 있습니다.

    public override void ViewWillAppear(bool animated)
    {
        base.ViewWillAppear(animated);
        this.NavigationController.SetNavigationBarHidden(true, true);
    }

    public override void ViewDidAppear(bool animated)
    {
        base.ViewDidAppear(animated);
        this.NavigationController.SetNavigationBarHidden(false, false);
        this.NavigationController.NavigationBar.Hidden = true;
    }

    public override void ViewWillDisappear(bool animated)
    {
        base.ViewWillDisappear(animated);
        this.NavigationController.SetNavigationBarHidden(true, false);
    }

다음은 사용자가 ViewController에서 슬라이드 할 때 제스처 인식기를 비활성화하는 방법입니다. viewWillAppear () 또는 ViewDidLoad () 메서드에 붙여 넣을 수 있습니다.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

참고 URL : https://stackoverflow.com/questions/24710258/no-swipe-back-when-hiding-navigation-bar-in-uinavigationcontroller

반응형