Programing

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

lottogame 2020. 9. 4. 08:03
반응형

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이 뭔가를 할 때까지) 몇 가지 방법으로 메인 런 루프를 트리거 할 수 있습니다.

The least intrusive solution is to call CFRunLoopWakeUp:

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

Or you can enqueue an empty block to the main queue:

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

It's funny, but if you shake the device, it'll also trigger the main loop (it has to process the motion events). Same thing with taps, but that's included in the original question :) Also, if the system updates the status bar (e.g. the clock updates, the WiFi signal strength changes etc.) that'll also wake up the main loop and present the view controller.

For anyone interested I wrote a minimal demonstration project of the issue to verify the runloop hypothesis: https://github.com/tzahola/present-bug

I've also reported the bug to Apple.


Check this out: https://devforums.apple.com/thread/201431 If you don't want to read it all - the solution for some people (including me) was to make the presentViewController call explicitly on the main thread:

Swift 4.2:

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

Objective-C:

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

Probably iOS7 is messing up the threads in didSelectRowAtIndexPath.


I bypassed it in Swift 3.0 by using the following code:

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

Calling [viewController view] on the view controller being presented did the trick for me.


I'd be curious to see what [self setUpViewForNextQuestion]; does.

You could try calling [self.correctViewController.view setNeedsDisplay]; at the end of your completion block in presentViewController.


I've wrote extension (category) with method swizzling for UIViewController that solves the issue. Thanks to AXE and NSHipster for implementation hints (swift/objective-c).

Swift

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)
    }
  }
}  

Objective-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

Check if your cell in the storyboard has Selection = none

If so, change it to blue or grey and it should work


XCode Vesion : 9.4.1, Swift 4.1

In my case this happen, when I tap cell and move for the another view. I debug into the deeper and it seems that happen inside viewDidAppear because of contains following code

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

then I added above code segment inside prepare(for segue: UIStoryboardSegue, sender: Any?) and work perfect.

Within my experience, my solution is, If we'll hope to do any new changes(eg. table reload, deselect selected cell etc.) for the tableview when again come back from second view, then use delegate instead of viewDidAppear and using above tableView.deselectRow code segment before moving second view controller

참고URL : https://stackoverflow.com/questions/21075540/presentviewcontrolleranimatedyes-view-will-not-appear-until-user-taps-again

반응형