iOS 7에서 뒤로 버튼을 변경하면 뒤로 이동하려면 스 와이프가 비활성화됩니다.


81

다음과 같은 사용자 지정 뒤로 버튼을 설정하는 iOS 7 앱이 있습니다.

    UIImage *backButtonImage = [UIImage imageNamed:@"back-button"];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];

    [backButton setImage:backButtonImage forState:UIControlStateNormal];
    backButton.frame = CGRectMake(0, 0, 20, 20);

    [backButton addTarget:self
                   action:@selector(popViewController)
         forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backBarButtonItem;

그러나 이렇게하면 iOS 7 "왼쪽에서 오른쪽으로 스 와이프"동작이 비활성화되어 이전 컨트롤러로 이동합니다. 사용자 정의 버튼을 설정하고이 제스처를 계속 활성화 할 수있는 방법을 아는 사람이 있습니까?

편집 : 대신 viewController.navigationItem.backBarButtonItem을 설정하려고했지만 내 사용자 지정 이미지가 표시되지 않는 것 같습니다.


나는 이것에 대한 적절한 해결책을 아직 찾지 못했다 ?? 좋은 해결책을 찾은 사람이 있고 그 이유를 설명하는 사람이 있습니까 ??.
user1010819 2013

잘 만들어진 타사 라이브러리 인 SwipeBack을 사용하는 것은 어떻습니까?
devxoul

답변:


82

중요 : 이것은 해킹입니다. 이 답변을 살펴 보는 것이 좋습니다 .

leftBarButtonItem나를 위해 일한 것을 할당 한 후 다음 라인을 호출하십시오 .

self.navigationController.interactivePopGestureRecognizer.delegate = self;

편집 :init 메서드 에서 호출하면 작동하지 않습니다 . in viewDidLoad또는 유사한 메서드를 호출해야합니다 .


뷰 컨트롤러에서 설정합니까? 아니면 내비게이션 컨트롤러? 동일한 문제가 있지만 작동하지 않는 것 같습니다.
Kevin Renskers 2013 년

1
@mixedCase 샘플 프로젝트를 다운로드 한 후 이제 문제를 이해했습니다. 코드는 컬렉션 뷰의 콘텐츠가 뷰 컨트롤러의 가로 너비를 초과하지 않는 한 작동합니다. 그러나 컬렉션 뷰가 가로로 스크롤 가능 해지면 interactivePopGestureRecognizer를 재정의합니다. 해결 방법을 찾을 수 있는지 확인하겠습니다.
Paul Hunter

8
이 코드에 문제가 있습니다. 5-10 번의 백 스 와이프 VC가 동결 된 후 오래 걸리지 않습니다. 어떤 해결책이 있습니까?
Timur Bernikovich

1
Timur Bernikowich 나도 똑같은 경험을했는데, 그 이유를 찾았습니까?
user1010819 2011

4
위임을로 self설정하면 class의 객체에서 설정이 해제됩니다 _UINavigationInteractiveTransition. 탐색 컨트롤러가 이미 전환중인 동안 팝업이 표시되지 않도록하는 것은 해당 개체의 책임입니다. 뒤로 버튼이 사용자 지정일 때이 제스처를 활성화 할 수 있는지 여부를 계속 조사 중입니다.
Saltymule 2013-12-02

56

가능한 경우 UINavigationBar의 backIndicatorImage 및 backIndicatorTransitionMaskImage 속성을 사용합니다. UIAppearanceProxy에서이를 설정하면 애플리케이션에서 동작을 쉽게 수정할 수 있습니다. 주름은 ios 7에서만 설정할 수 있지만 어쨌든 ios 7에서만 팝 제스처를 사용할 수 있기 때문에 효과가 있습니다. 일반적인 iOS 6 스타일은 그대로 유지할 수 있습니다.

UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:@selector(setBackIndicatorImage:)])
{
    appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:@"back"];
    appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];
    //sets back button color
    appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
    //do ios 6 customization
}

interactivePopGestureRecognizer의 델리게이트를 조작하려고하면 많은 문제가 발생합니다.


4
감사합니다 Dan, 이것은 정말 중요하며 받아 들여지는 대답이어야합니다. 단순히 대리인을 재 지정함으로써 당신은 많은 이상한 행동을 할 수 있습니다. 특히 사용자가 topViewController.
Chris Wagner

1
이것은 당신이 원하는 것은 뒤로 버튼 이미지를 변경하는 것이라고 가정하는 솔루션입니다. 그러나 뒤로 버튼 텍스트 및 / 또는 뒤로 버튼을 클릭 할 때 수행되는 작업을 변경하려면 어떻게해야합니까?
user102008 2014 년

이 시점에서 UINavigationItem.leftBarButtonItem을 설정하면 더 나은 서비스를받을 수 있습니다. Google 또는 stackoverflow에서 leftBarButtonItem을 검색하면 다양한 답변을 찾을 수 있습니다.
Saltymule

예를 들어 사용자가 만든 탐색 모음에 영향을주지 않고 모양 변경을 자신의 UI로 제한하려면 ABPeoplePickerNavigationController사용자 지정 UINavigationController하위 클래스를 사용할 수 있습니다 .[[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorImage:[UIImage imageNamed:@"btn_back_arrow"]]; [[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"btn_back_arrow_highlighted"]];
tom

2
불행히도 iOS8에서는 작동하지 않습니다. 뒤로 버튼은 내 사용자 지정 이미지의 모양을 변경하지 않습니다.
Lensflare 2014-10-27

29

UINavigationController를 하위 클래스로 만드는 이 솔루션 http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ 을 보았습니다 . 컨트롤러가 제자리에 놓이기 전에 스 와이프하는 경우를 처리하므로 더 나은 솔루션으로 충돌이 발생합니다.

이 외에도 루트 뷰 컨트롤러에서 스 와이프를 수행하면 (하나를 누른 후 다시 뒤로) UI가 응답하지 않는 것으로 나타났습니다 (위의 답변에서도 동일한 문제).

따라서 서브 클래 싱 된 UINavigationController의 코드는 다음과 같아야합니다.

@implementation NavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak NavigationController *weakSelf = self;

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.delegate = weakSelf;
        self.delegate = weakSelf;
    }
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    // Hijack the push method to disable the gesture
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    [super pushViewController:viewController animated:animated];
}

#pragma mark - UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
    // Enable the gesture again once the new controller is shown
    self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}

@end

1
루트 뷰 컨트롤러에서 스 와이프하는 데 동일한 문제가 발생했습니다. 감사합니다!
Michael Rose

전체 페이지에서 최고의 솔루션. 완벽하게 잘 작동합니다. 감사합니다
Shahid Iqbal

19

나는 사용한다

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]];

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];

2
나는 이것이 현재 Apple 코드의 버그로 인해 64 비트 모드에서 문제를 일으킨다는 의견을 다른 곳에서 보았습니다. 방금 경고를 게시 할 것이라고 생각했습니다
Peter Johnson

@PeterJohnson 어떤 종류의 버그?
art-divin 2014

단순히 최고의 솔루션!
Igotit

지금까지 가장 간단한 솔루션.
tounaobun 2015-07-24

6

또한 뒤로 버튼을 숨기고 사용자 정의 leftBarItem으로 바꿉니다.
푸시 작업 후 interactivePopGestureRecognizer 대리자를 제거하면 나를 위해 일했습니다.

[self.navigationController pushViewController:vcToPush animated:YES];

// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}

4
이 방법의 한 가지 문제는 루트 뷰에서 가장자리 이동을 수행하면 UI가 잠긴다는 것입니다. 동일한 뷰의 여러 인스턴스를 탐색 스택에 푸시하기 때문에이 문제가 발생했습니다.
Nikola Lajic 2013 년

@MrNickBarker 알려 주셔서 감사합니다! 정확한 시나리오를 설명해 주시겠습니까? 루트 뷰 컨트롤러를 스 와이프 할 때 재현 할 수 없었습니다
avishic

4
viewDidLoad 메서드에서 설정했습니다. 내 마지막 해결책은 탐색 스택에 둘 이상의 뷰 컨트롤러가있는 경우 'gestureRecognizerShouldBegin'에 대해 true를 반환하는 델리게이트로 다른 클래스를 설정하는 것입니다.
Nikola Lajic 2013 년

MrNickBarker 어떤 이유가 멈춤 나는 같은 경험을하고 있습니다
user1010819 2011

6
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

이것은 http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks 에서 왔지만 몇 가지 버그가 발생합니다.

  1. 화면의 왼쪽 가장자리에서 스 와이프 할 때 다른 viewController를 navigationController에 밀어 넣습니다.
  2. 또는 topViewController가 navigationController에서 팝업 될 때 화면의 왼쪽 가장자리에서 안으로 스 와이프합니다.

예를 들어 navigationController의 rootViewController가 표시되면 화면 왼쪽 가장자리에서 안쪽으로 스 와이프하고 무언가를 탭 (빠르게)하여 다른 ViewController를 navigationController로 푸시 한 다음

  • rootViewController는 터치 이벤트에 응답하지 않습니다.
  • anotherViewController는 표시되지 않습니다.
  • 화면 가장자리에서 다시 스 와이프하면 anotherViewController가 표시됩니다.
  • 사용자 정의 뒤로 버튼을 탭하여 anotherViewController를 팝업하고 충돌합니다!

따라서 다음 과 같이 UIGestureRecognizerDelegate메소드를 구현해야합니다 self.navigationController.interactivePopGestureRecognizer.delegate.

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    }
    return YES;
}

1
SwipeBack 은 이러한 문제에 대한 해결책입니다.
devxoul

6

다음은 Nick H247의 답변의 swift3 버전입니다.

class NavigationController: UINavigationController {
  override func viewDidLoad() {
    super.viewDidLoad()
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.delegate = self
      delegate = self
    }
  }

  override func pushViewController(_ viewController: UIViewController, animated: Bool) {
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.isEnabled = false
    }
    super.pushViewController(viewController, animated: animated)
  }
}

extension NavigationController: UINavigationControllerDelegate {
  func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
  }
}

extension NavigationController: UIGestureRecognizerDelegate {}

3

시험 self.navigationController.interactivePopGestureRecognizer.enabled = YES;


아니요, 이미 활성화되어 있습니다. 문제는 대리인이 제스처 인식기 시작을 허용하지 않는 것 같습니다. 여기에 다른 답변으로 내 솔루션을 추가했습니다.
avishic

avishic 대리자 설정이 작동하는 이유를 설명해 주시겠습니까? .. 나는 같은 문제에 갇혀 있습니다.
user1010819 2011

1

나는 이것을 쓰지 않았지만 다음 블로그가 많은 도움을 받았고 사용자 정의 탐색 버튼으로 문제를 해결했습니다.

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

요약하면 그는 팝 제스처 대리자를 사용하는 사용자 지정 UINavigationController를 구현합니다. 매우 깨끗하고 휴대 가능합니다!

암호:

@interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
@end

@implementation CBNavigationController

- (void)viewDidLoad
{
  __weak CBNavigationController *weakSelf = self;

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
  {
    self.interactivePopGestureRecognizer.delegate = weakSelf;
    self.delegate = weakSelf;
  }
}

// Hijack the push method to disable the gesture

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = NO;

  [super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
  // Enable the gesture again once the new controller is shown

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

편집하다. 사용자가 루트 뷰 컨트롤러에서 왼쪽으로 스 와이프하려고 할 때 발생하는 문제에 대한 수정을 추가했습니다.

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
        self.topViewController == [self.viewControllers firstObject] &&
        gestureRecognizer == self.interactivePopGestureRecognizer) {

        return NO;
    }

    return YES;
}

탐색 컨트롤러의 루트보기 컨트롤러에서 왼쪽으로 스 와이프하면 UI가 정지됩니다.
JohnVanDijk

@JohnVanDijk 나는 그 문제를 해결하기 위해 구현했다고 생각하는 수정으로 답변을 편집했습니다. 오래되었지만 말이됩니다. 기본적으로 상위 뷰 컨트롤러가 루트 뷰 컨트롤러 인 경우 'interactivePopGestureRecognizer'에 응답하지 않습니다
kgaidis

그것은 내 뒤로 버튼을 숨기고
모하메드 후세인

1

RootView

override func viewDidAppear(_ animated: Bool) {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}

ChildView

override func viewDidLoad() {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
} 

extension ChildViewController: UIGestureRecognizerDelegate {}

1

이 로직을 사용하여 스 와이프 제스처를 활성화 또는 비활성화합니다.

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        if (self.navigationController.viewControllers.count > 1)
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}

0

현재보기 컨트롤러를 대화 형 팝 제스처의 대리자로 할당하는 비슷한 문제가 있었지만 푸시 된보기 또는 탐색 스택의보기 아래에있는보기에서 제스처를 중단했습니다. 이 문제를 해결 한 방법은 대리자를에서 -viewDidAppear설정 한 다음에서 nil로 설정하는 것입니다 -viewWillDisappear. 이를 통해 다른 뷰가 올바르게 작동 할 수있었습니다.


0

Apple의 기본 마스터 / 디테일 프로젝트 템플릿을 사용하고 있다고 가정 해 봅시다. 여기서 master는 테이블 뷰 컨트롤러이고 탭하면 디테일 뷰 컨트롤러가 표시됩니다.

디테일 뷰 컨트롤러에 나타나는 뒤로 버튼을 사용자 지정하려고합니다. 뒤로 버튼 의 이미지 , 이미지 색상 , 텍스트 , 텍스트 색상 및 글꼴 을 사용자 정의하는 방법 입니다.


이미지, 이미지 색상, 텍스트 색상 또는 글꼴을 전역 적으로 변경하려면 뷰 컨트롤러가 생성되기 전에 호출되는 위치에 다음을 배치합니다 (예 : application:didFinishLaunchingWithOptions:좋은 위치).

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];

    // change the back button, using default tint color
    navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:@"back"];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the back button, using the color inside the original image
    navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the tint color of everything in a navigation bar
    navigationBarAppearance.tintColor = [UIColor greenColor];

    // change the font in all toolbar buttons
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };

    [[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];

    return YES;
}

를 사용 appearanceWhenContainedIn:하여 이러한 변경 사항의 영향을받는 뷰 컨트롤러를 더 많이 제어 할 수 있지만 [DetailViewController class], DetailViewController가 아닌 UINavigationController 내부에 포함되어 있으므로 전달할 수 없습니다 . 즉, 영향을받는 항목을 더 많이 제어하려면 UINavigationController를 하위 클래스로 만들어야합니다.

특정 뒤로 단추 항목의 텍스트 또는 글꼴 / 색상을 사용자 정의하려면 MasterViewController 에서 수행해야합니다 (DetailViewController가 아닙니다!). 버튼이 DetailViewController에 나타나기 때문에 이것은 직관적이지 않은 것 같습니다. 그러나이를 사용자 정의하는 방법이 navigationItem에 속성을 설정하는 것임을 이해하면 더 이해하기 시작합니다.

- (void)viewDidLoad { // MASTER view controller
    [super viewDidLoad];

    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:@"Testing"
                                                                   style:UIBarButtonItemStylePlain
                                                                  target:nil
                                                                  action:nil];
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };
    [buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = buttonItem;
}

참고 : self.navigationItem.backBarButtonItem을 설정 한 후 titleTextAttributes를 설정하려고하면 작동하지 않는 것 같으므로이 속성에 값을 할당하기 전에 설정해야합니다.


0

'UINavigationController'의 하위 클래스 인 'TTNavigationViewController'클래스를 만들고이 클래스의 기존 탐색 컨트롤러를 스토리 보드 / 클래스, 클래스의 예제 코드로 만듭니다.

    class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.setNavigationBarHidden(true, animated: false)

    // enable slide-back
    if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
        self.interactivePopGestureRecognizer?.isEnabled = true
        self.interactivePopGestureRecognizer?.delegate  = self
    }
}

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.