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

.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];
    }];
}



답변

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

실제로는 매우 미묘한 버그입니다. 왜냐하면 코드에 피드백 애니메이션, 타이머 등이 ​​조금이라도 있다면 런 루프가 이러한 소스에 의해 유지되기 때문에이 문제가 드러나지 않기 때문입니다. 나는 사용하여 문제를 발견 한 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에 버그를보고했습니다.


답변

이것을 확인하십시오 : 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.


답변

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

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


답변

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


답변

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

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


답변

문제를 해결하는 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


답변

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

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