presentViewController : animated : YES보기는 사용자가 다시 탭할 때까지 표시되지 않습니다.


93

.NET에서 이상한 동작이 발생 presentViewController:animated:completion합니다. 제가 만드는 것은 본질적으로 추측 게임입니다.

나는이 UIViewController(frequencyViewController)를 포함하는 UITableView(frequencyTableView을). 사용자가 정답이 포함 된 questionTableView의 행을 탭하면 뷰 (correctViewController)가 인스턴스화되고 해당 뷰가 모달 뷰로 화면 하단에서 위로 이동해야합니다. 이것은 사용자에게 정답이 있음을 알리고 다음 질문에 대한 준비가 된 frequencyViewController를 재설정합니다. 다음 질문을 표시하기 위해 버튼을 누를 때 correctViewController가 닫힙니다.

이 모든 것은 매번 올바르게 작동하며 올바른 ViewController의 뷰 presentViewController:animated:completionanimated:NO.

내가 설정 animated:YES하면 correctViewController가 초기화되고 viewDidLoad. 그러나 viewWillAppear, viewDidAppear및의 완료 블록은 presentViewController:animated:completion호출되지 않습니다. 두 번째 탭을 할 때까지 앱은 여전히 ​​frequencyViewController를 표시하고 있습니다. 이제 viewWillAppear, viewDidAppear 및 완료 블록이 호출됩니다.

나는 조금 더 조사했고, 그것이 계속되게하는 것은 단지 또 다른 탭이 아니다. iPhone을 기울이거나 흔들면 viewWillLoad 등을 트리거 할 수도 있습니다. 진행되기 전에 다른 사용자 입력을 기다리는 것과 같습니다. 이것은 실제 iPhone과 시뮬레이터에서 발생하며, 진동 명령을 시뮬레이터에 전송하여 증명했습니다.

이 문제에 대해 어떻게해야할지 모르겠습니다. 누구든지 도와 줄 수있는 어떤 도움이라도 정말 감사하겠습니다.

감사

여기 내 코드가 있습니다. 아주 간단합니다 ...

이것은 questionTableView에 대한 대리자 역할을하는 questionViewController의 코드입니다.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];            
    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        self.correctViewController = [[HFBCorrectViewController alloc] init];
        self.correctViewController.delegate = self;
        [self presentViewController:self.correctViewController animated:YES completion:^(void){
            NSLog(@"Completed Presenting correctViewController");
            [self setUpViewForNextQuestion];
        }];
    }
}

이것은 correctViewController의 전체입니다.

@implementation HFBCorrectViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        // Custom initialization
        NSLog(@"[HFBCorrectViewController initWithNibName:bundle:]");
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    NSLog(@"[HFBCorrectViewController viewDidLoad]");
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"[HFBCorrectViewController viewDidAppear]");
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)close:(id)sender
{
    NSLog(@"[HFBCorrectViewController close:sender:]");
    [self.delegate didDismissCorrectViewController];
}


@end

편집하다:

이전에이 질문을 찾았습니다. UITableView 및 presentViewController가 표시하는 데 2 ​​번의 클릭이 필요합니다.

didSelectRow코드를 이것으로 변경하면 애니메이션과 함께 매우 시간이 걸립니다. 그러나 그것은 지저분하고 왜 처음부터 작동하지 않는지 이해가되지 않습니다. 그래서 저는 그것을 대답으로 간주하지 않습니다 ...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];
        // [cell setAccessoryType:(UITableViewCellAccessoryType)]

    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);

        ////////////////////////////
        // BELOW HERE ARE THE CHANGES
        [self performSelector:@selector(showCorrectViewController:) withObject:nil afterDelay:0];
    }
}

-(void)showCorrectViewController:(id)sender
{
    self.correctViewController = [[HFBCorrectViewController alloc] init];
    self.correctViewController.delegate = self;
    self.correctViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentViewController:self.correctViewController animated:YES completion:^(void){
        NSLog(@"Completed Presenting correctViewController");
        [self setUpViewForNextQuestion];
    }];
}

1
나도 같은 문제가있어. NSLogs를 사용하여 실행하는 데 오랜 시간이 걸리는 메서드를 호출하지 않는지 확인했습니다. presentViewController:트리거해야하는 애니메이션이 큰 지연으로 시작되는 것 같습니다. 이것은 iOS 7의 버그로 보이며 Apple Dev Forums에서도 논의됩니다.
Theo

나도 같은 문제가있어. iOS7 버그처럼 보임-iOS6에서는 발생하지 않습니다. 또한 제 경우에는 프레젠테이션 뷰 컨트롤러를 연 후 처음에만 발생합니다. @Theo, Apple Dev Forums에 대한 링크를 제공 할 수 있습니까?
AX

답변:


158

오늘도 같은 문제가 발생했습니다. 나는 주제를 파헤 쳤고 그것이 주된 런 루프가 잠든 것과 관련이있는 것 같다.

실제로는 매우 미묘한 버그입니다. 왜냐하면 코드에 피드백 애니메이션, 타이머 등이 ​​조금이라도 있다면 런 루프가 이러한 소스에 의해 유지되기 때문에이 문제가 드러나지 않기 때문입니다. 나는 사용하여 문제를 발견 한 UITableViewCell그 있던 selectionStyle으로 설정을 UITableViewCellSelectionStyleNone더 선택 애니메이션 행 선택 핸들러 실행 된 후 runloop을 유발하지 그래서.

이를 고치기 위해 (Apple이 뭔가를 할 때까지) 몇 가지 방법으로 메인 런 루프를 트리거 할 수 있습니다.

가장 방해가되지 않는 해결책은 다음을 호출하는 것입니다 CFRunLoopWakeUp.

[self presentViewController:vc animated:YES completion:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());

또는 빈 블록을 기본 대기열에 넣을 수 있습니다.

[self presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{});

재미 있긴하지만 장치를 흔들면 메인 루프도 트리거됩니다 (동작 이벤트를 처리해야 함). 탭도 마찬가지지만 원래 질문에 포함되어 있습니다. :) 또한 시스템이 상태 표시 줄을 업데이트하면 (예 : 시계 업데이트, WiFi 신호 강도 변경 등) 메인 루프를 깨우고보기를 표시합니다. 제어 장치.

관심있는 사람을 위해 나는 runloop 가설을 확인하기 위해 문제에 대한 최소한의 데모 프로젝트를 작성했습니다 : https://github.com/tzahola/present-bug

또한 Apple에 버그를보고했습니다.


감사합니다 Tamás. 훌륭한 정보입니다. 때로는 실행 루프를 시작하기 위해 탭해야하거나 때로는 그냥 기다려야하는 이유를 설명합니다. 버그가 수정 될 때까지 우리가 할 수있는 다른 일이없는 것 같기 때문에 이것을 해결책으로 표시했습니다.
HalfNormalled

애플은 큰 구덩이를 파고, 나는 그 구덩이에 빠지고 심하게 상처 받았다.
OpenThread

7
OMG, 너무 재밌어! BTW,이 버그는 여전히 존재합니다.
igrrik

1
나는 똑같은 문제가 정말 짜증나고 고맙게도 해결되었습니다.
Mark

1
이 문제가 iOS 13에서 여전히 발생 함을 확인했습니다
Eric Horacek

69

이것을 확인하십시오 : https://devforums.apple.com/thread/201431 모든 것을 읽고 싶지 않다면-일부 사람들 (나를 포함하여)을위한 해결책 presentViewController은 주 스레드에서 명시 적으로 호출 하는 것입니다 .

Swift 4.2 :

DispatchQueue.main.async { 
    self.present(myVC, animated: true, completion: nil)
}

목표 -C :

dispatch_async(dispatch_get_main_queue(), ^{
    [self presentViewController:myVC animated:YES completion:nil];
});

아마도 iOS7은 didSelectRowAtIndexPath.


와우-이것은 나를 위해 일했습니다. 나도 지연을보고 있었다. 왜 이것이 필요한지 이해할 수 없습니다. 게시 해 주셔서 감사합니다.
drudru

4
이 문제에 대해 Apple에 레이더를 제출했습니다. rdar : // 19563577 openradar.appspot.com/19563577
mluisbrown

1
Im iOS 8이고 이것은 나를 위해 수정되지 않습니다. 항상 주 스레드에 있다고보고하지만 여전히 설명 된 문제를 볼 수 있습니다.
로버트

7

다음 코드를 사용하여 Swift 3.0에서 우회했습니다.

DispatchQueue.main.async { 
    self.present(UIViewController(), animated: true, completion: nil)
}

1
문제를 해결합니다. present클로저 콜백을 호출했기 때문에 동일한 문제가 발생했습니다 .
Jeremy Piednoel

2

[viewController view]표시되는 뷰 컨트롤러를 호출 하면 트릭이 생겼습니다.


이것은 iOS 8에서 저에게 효과적이었습니다. dispatch_async보다 낫다고 생각합니다.
로버트

0

무엇을하는지 궁금합니다 [self setUpViewForNextQuestion];.

[self.correctViewController.view setNeedsDisplay];에서 완료 블록의 끝에서 호출 을 시도 할 수 presentViewController있습니다.


현재 setUpViewForNextQuestion은 tableview에서 reloadData를 호출합니다 (잘못된 추측으로 인해 모든 배경색이 빨간색으로 설정된 경우 다시 흰색으로 변경). 그러나 어떤 변화가 어떤 차이를 만들까요? 문제는 사용이 다시 탭할 때까지 correctViewController가 나타나지 않으므로이 완료 블록은 두 번째 탭 이후까지 호출되지 않는다는 것입니다.
HalfNormalled

나는 당신의 제안을 시도했지만 아무런 차이가 없었습니다. 내가 말했듯이보기가 표시되지 않으므로 두 번째 탭 또는 흔들기 제스처가있을 때까지 완료 블록이 호출되지 않습니다.
HalfNormalled

흠. 우선 중단 점을 사용하여 두 번째 탭까지 프레젠테이션 코드가 호출되지 않았는지 확인합니다. (첫 번째 탭에서 호출되지만 나중에 화면에 그려지지 않음). 후자가 사실이면 프레젠테이션이 주 스레드에서 발생하도록합니다. "performSelectorWithDelay : 0"을 사용하면 앱이 실행하기 전에 다음 실행 루프 반복까지 대기하도록 강제합니다. 스레딩 관련 일 수 있습니다.
Nick

고마워, 닉. 중단 점을 사용하여 어떻게 확인합니까? 현재 각 메서드가 호출되는시기를 확인하기 위해 NSLog를 사용하고 있습니다. 이것이 내가 지금 가지고있는 것입니다 : Tap 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] 이제 다음까지 아무 일도 일어나지 않습니다 : Tap 218:05:00.515 Frequency[641:60b] [HFBCorrectViewController viewDidAppear] 18:05:00.516 Frequency[641:60b] Completed Presenting correctViewController 18:05:00.517 Frequency[641:60b] [HFBFrequencyViewController setUpViewForNextQuestion]
HalfNormalled

죄송합니다. 더 명시 적이어야합니다. 중단 점을 사용하는 방법을 이해합니다. NSLog와 같은 이야기를 듣습니다. 올바른 ViewController의 viewDidLoad 및 viewDidAppear에 중단 점을 넣었습니다. viewDidLoad에서 중단되고 계속하면 앉아서 다른 탭을 기다린 다음 viewDidAppear에서 중단됩니다. 때로는 탭이 필요하지 않은 것 같고 시간 기반입니다. 다른 호출이 호출되는 것을 살펴보고 단계를 수행 할 때 쯤이면 viewDidLoad를 호출합니다.
HalfNormalled

0

문제를 해결하는 UIViewController에 대한 방법을 사용하여 확장 (카테고리)을 작성했습니다. 구현 힌트 ( swift / objective-c )에 대한 AX 및 NSHipster에게 감사드립니다 .

빠른

extension UIViewController {

 override public class func initialize() {
    struct DispatchToken {
      static var token: dispatch_once_t = 0
    }
    if self != UIViewController.self {
      return
    }
    dispatch_once(&DispatchToken.token) {
      let originalSelector = Selector("presentViewController:animated:completion:")
      let swizzledSelector = Selector("wrappedPresentViewController:animated:completion:")

      let originalMethod = class_getInstanceMethod(self, originalSelector)
      let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

      let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

      if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
      }
      else {
        method_exchangeImplementations(originalMethod, swizzledMethod)
      }
    }
  }

  func wrappedPresentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
    dispatch_async(dispatch_get_main_queue()) {
      self.wrappedPresentViewController(viewControllerToPresent, animated: flag, completion: completion)
    }
  }
}  

목표 -C

#import <objc/runtime.h>

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(presentViewController:animated:completion:);
        SEL swizzledSelector = @selector(wrappedPresentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)wrappedPresentViewController:(UIViewController *)viewControllerToPresent 
                             animated:(BOOL)flag 
                           completion:(void (^ __nullable)(void))completion {
    dispatch_async(dispatch_get_main_queue(),^{
        [self wrappedPresentViewController:viewControllerToPresent
                                  animated:flag 
                                completion:completion];
    });

}

@end

0

스토리 보드의 셀에 Selection = none인지 확인

그렇다면 파란색 또는 회색으로 변경하면 작동합니다.


0

XCode Vesion : 9.4.1, Swift 4.1

제 경우에는 셀을 탭하고 다른보기로 이동할 때 이런 일이 발생합니다. 나는 더 깊게 디버그 viewDidAppear하고 다음 코드가 포함되어 있기 때문에 내부에서 일어나는 것 같습니다.

if let indexPath = tableView.indexPathForSelectedRow {
   tableView.deselectRow(at: indexPath, animated: true)
}

그런 다음 위의 코드 세그먼트를 내부에 추가 prepare(for segue: UIStoryboardSegue, sender: Any?)하고 완벽하게 작동합니다.

내 경험상 내 솔루션은 두 번째 뷰에서 다시 돌아올 때 테이블 뷰에 대해 새로운 변경 (예 : 테이블 다시로드, 선택한 셀 선택 취소 등)을 수행하려는 경우 viewDidAppeartableView.deselectRow코드 대신 위임을 사용하는 것입니다. 두 번째 뷰 컨트롤러를 이동하기 전 세그먼트

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.