PresentViewController: animation: YES view sẽ không xuất hiện cho đến khi người dùng nhấn lại


93

Tôi nhận được một số hành vi kỳ lạ với presentViewController:animated:completion. Những gì tôi đang làm về cơ bản là một trò chơi đoán.

Tôi có một UIViewController(frequencyViewController) chứa một UITableView(frequencyTableView). Khi người dùng chạm vào hàng trong questionTableView chứa câu trả lời đúng, một chế độ xem (đúngViewController) sẽ được khởi tạo và chế độ xem của nó sẽ trượt lên từ cuối màn hình, dưới dạng một chế độ xem phương thức. Điều này cho người dùng biết họ có câu trả lời chính xác và đặt lại tần sốViewController đằng sau nó sẵn sàng cho câu hỏi tiếp theo. trueViewController bị loại bỏ khi nhấn nút để hiển thị câu hỏi tiếp theo.

Tất cả điều này đều hoạt động chính xác mọi lúc và chế độ xem của correctViewController xuất hiện ngay lập tức miễn là presentViewController:animated:completionanimated:NO.

Nếu tôi đặt animated:YES, thì trueViewController được khởi tạo và thực hiện các cuộc gọi tới viewDidLoad. Tuy nhiên viewWillAppear, viewDidAppearvà khối hoàn thành từ presentViewController:animated:completionkhông được gọi. Ứng dụng chỉ nằm ở đó và vẫn hiển thị tần sốViewController cho đến khi tôi nhấn lần thứ hai. Bây giờ, viewWillAppear, viewDidAppear và khối hoàn thành được gọi.

Tôi đã điều tra thêm một chút và không chỉ một lần nhấn nữa sẽ khiến nó tiếp tục. Có vẻ như nếu tôi nghiêng hoặc lắc iPhone của mình, điều này cũng có thể khiến nó kích hoạt viewWillLoad, v.v. Nó giống như việc chờ đợi bất kỳ thao tác nhập liệu nào khác của người dùng trước khi nó tiến hành. Điều này xảy ra trên iPhone thực và trong trình mô phỏng, điều mà tôi đã chứng minh bằng cách gửi lệnh lắc đến trình mô phỏng.

Tôi thực sự lúng túng không biết phải làm gì về điều này ... Tôi thực sự đánh giá cao bất kỳ sự giúp đỡ nào mà bất kỳ ai có thể cung cấp.

Cảm ơn

Đây là mã của tôi. Nó khá đơn giản ...

Đây là mã trong questionViewController hoạt động như đại biểu cho questionTableView

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

Đây là toàn bộ của trueViewController

@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

Biên tập:

Tôi đã tìm thấy câu hỏi này trước đó: UITableView và presentViewController mất 2 lần nhấp để hiển thị

Và nếu tôi thay đổi didSelectRowmã của mình thành mã này, nó hoạt động rất nhanh với hoạt ảnh ... Nhưng nó lộn xộn và không có ý nghĩa tại sao nó không hoạt động ngay từ đầu. Vì vậy, tôi không tính đó là câu trả lời ...

- (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
Tôi có cùng một vấn đề. Tôi đã kiểm tra với NSLogs rằng tôi không gọi bất kỳ phương thức nào mất nhiều thời gian để thực thi. Có vẻ như đó chỉ là hoạt ảnh presentViewController:được cho là kích hoạt bắt đầu với độ trễ lớn. Đây dường như là một lỗi trong iOS 7 và cũng được thảo luận trong Diễn đàn nhà phát triển của Apple.
Theo

Tôi có cùng một vấn đề. Có vẻ như một lỗi iOS7 - không xảy ra trên iOS6. Ngoài ra, trong trường hợp của tôi, nó chỉ xảy ra lần đầu tiên sau khi tôi mở bộ điều khiển chế độ xem trình bày. @Theo, bạn có thể cung cấp liên kết đến Diễn đàn nhà phát triển của Apple không?
AX

Câu trả lời:


157

Tôi đã gặp vấn đề tương tự ngày hôm nay. Tôi đã đào sâu vào chủ đề và có vẻ như nó liên quan đến việc đường chạy chính đang ngủ.

Trên thực tế, đó là một lỗi rất nhỏ, bởi vì nếu bạn có hoạt ảnh phản hồi nhỏ nhất, bộ hẹn giờ, v.v. trong mã của mình, vấn đề này sẽ không xuất hiện bởi vì runloop sẽ được giữ nguyên bởi các nguồn này. Tôi đã tìm thấy vấn đề bằng cách sử dụng một UITableViewCellđã được selectionStyleđặt thành UITableViewCellSelectionStyleNone, để không có hoạt ảnh lựa chọn nào kích hoạt vòng chạy sau khi trình xử lý chọn hàng chạy.

Để khắc phục nó (cho đến khi Apple thực hiện điều gì đó), bạn có thể kích hoạt runloop chính bằng một số cách:

Giải pháp ít xâm nhập nhất là gọi CFRunLoopWakeUp:

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

Hoặc bạn có thể xếp một khối trống vào hàng đợi chính:

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

Thật buồn cười, nhưng nếu bạn lắc thiết bị, nó cũng sẽ kích hoạt vòng lặp chính (nó phải xử lý các sự kiện chuyển động). Điều tương tự với các lần nhấn, nhưng điều đó được bao gồm trong câu hỏi ban đầu :) Ngoài ra, nếu hệ thống cập nhật thanh trạng thái (ví dụ: cập nhật đồng hồ, cường độ tín hiệu WiFi thay đổi, v.v.) cũng sẽ đánh thức vòng lặp chính và hiển thị chế độ xem bộ điều khiển.

Đối với bất kỳ ai quan tâm, tôi đã viết một dự án trình diễn tối thiểu về vấn đề này để xác minh giả thuyết về đường chạy: https://github.com/tzahola/present-bug

Tôi cũng đã báo cáo lỗi cho Apple.


Cảm ơn Tamás. Đó là thông tin tuyệt vời. Nó giải thích tại sao đôi khi tôi cần phải nhấn để bắt đầu vòng chạy hoặc đôi khi chỉ cần đợi và nó sẽ hoạt động. Tôi đã đánh dấu đây là giải pháp vì có vẻ như chúng tôi không thể làm gì khác cho đến khi lỗi được sửa.
HalfNormalled

Apple đào một cái hố lớn, tôi rơi vào đó và cảm thấy bị thương nặng.
OpenThread

7
OMG, điều này thật vui nhộn! BTW, lỗi này vẫn tồn tại.
igrrik

1
Tôi đã gặp cùng một vấn đề thực sự gây khó chịu, may mắn là điều này đã khắc phục được nó.
Đánh dấu

1
Đã xác nhận rằng sự cố này vẫn xảy ra trong iOS 13
Eric Horacek

69

Hãy xem phần này: https://devforums.apple.com/thread/201431 Nếu bạn không muốn đọc hết - giải pháp cho một số người (bao gồm cả tôi) làpresentViewController cuộc gọi một cách rõ ràng trên chuỗi chính:

Swift 4.2:

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

Mục tiêu-C:

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

Có lẽ iOS7 đang làm rối tung các luồng trong didSelectRowAtIndexPath.


wow - điều này làm việc cho tôi. Tôi cũng thấy sự chậm trễ. Tôi không thể hiểu tại sao điều này lại cần thiết. Cảm ơn vì đã đăng bài này.
drudru

4
Filed một radar với Apple về vấn đề này: rdar: // 19563577 openradar.appspot.com/19563577
mluisbrown

1
Tôi đang sử dụng iOS 8 và điều này không khắc phục được sự cố đối với tôi - nó luôn báo cáo đang ở trên chuỗi chính, nhưng tôi vẫn thấy sự cố được mô tả.
Robert

7

Tôi đã bỏ qua nó trong Swift 3.0 bằng cách sử dụng mã sau:

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

1
nó giải quyết vấn đề. Tôi đã gặp vấn đề tương tự vì tôi đang gọi presenttrong một cuộc gọi lại đóng.
Jeremy Piednoel

2

Gọi [viewController view]trên bộ điều khiển chế độ xem đang được trình bày đã thực hiện một mẹo nhỏ cho tôi.


Điều này đã làm việc cho tôi trên iOS 8. Tôi nghĩ nó tốt hơn so với send_async.
Robert

0

Tôi rất tò mò muốn xem những gì [self setUpViewForNextQuestion]; sẽ xảy ra.

Bạn có thể thử gọi [self.correctViewController.view setNeedsDisplay];vào cuối khối hoàn thành của mình trong presentViewController.


Ngay bây giờ, setUpViewForNextQuestion chỉ gọi reloadData trên tableview (để thay đổi tất cả các màu nền trở lại màu trắng nếu bất kỳ màu nào đã được đặt thành màu đỏ do đoán sai). Nhưng bất kỳ thay đổi nào có tạo ra sự khác biệt? Vấn đề ở đây là trueViewController không xuất hiện cho đến khi nhấn lại sử dụng, vì vậy khối hoàn thành này sẽ không được gọi cho đến sau lần nhấn thứ hai đó.
HalfNormalled

Tôi đã thử gợi ý của bạn, nhưng nó không tạo ra sự khác biệt nào. Như tôi đã nói, chế độ xem không xuất hiện, vì vậy khối hoàn thành không được gọi cho đến sau lần nhấn thứ hai hoặc cử chỉ lắc.
HalfNormalled

hmm. đối với người mới bắt đầu, tôi sẽ sử dụng các điểm ngắt để xác nhận rằng mã bản trình bày của bạn không được gọi cho đến lần nhấn thứ hai. (so với được gọi ở lần nhấn đầu tiên, nhưng không được vẽ trên màn hình cho đến một thời gian sau). nếu điều sau là đúng, hãy thử buộc bản trình bày của bạn diễn ra trên chuỗi chính. Khi bạn sử dụng "performanceSelectorWithDelay: 0" buộc ứng dụng phải đợi cho đến lần lặp tiếp theo của vòng lặp chạy trước khi thực thi. Nó có thể liên quan đến luồng.
Nick

Cảm ơn, Nick. Làm cách nào để kiểm tra bằng cách sử dụng các điểm ngắt? Hiện tại, tôi đang sử dụng NSLog để kiểm tra khi nào mỗi phương thức đang được gọi. Đây là những gì tôi có bây giờ: Nhấn vào 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] Bây giờ không có gì xảy ra cho đến khi: Nhấn vào 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

Xin lỗi, lẽ ra phải rõ ràng hơn. Tôi hiểu cách sử dụng các điểm ngắt. Tôi nhận được câu chuyện tương tự như với NSLog. Tôi đặt các điểm ngắt trên viewDidLoad và viewDidAppear trong đúngViewController. Nó bị vỡ trên viewDidLoad, sau đó khi tôi tiếp tục, nó sẽ nằm và chờ một lần nhấn khác, sau đó ngắt trên viewDidAppear. Đôi khi nó dường như không cần một lần nhấn và tùy thuộc vào thời gian, khi tôi bước qua mọi thứ để xem những gì khác đang được gọi, thì việc gọi viewDidLoad đã hoàn tất.
HalfNormalled

0

Tôi đã viết tiện ích mở rộng (danh mục) với phương pháp swizzling cho UIViewController để giải quyết vấn đề. Cảm ơn AX và NSHipster về các gợi ý triển khai ( nhanh chóng / mục tiêu-c ).

Nhanh

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

0

Kiểm tra xem ô của bạn trong bảng phân cảnh có Lựa chọn = không

Nếu vậy, hãy đổi nó thành xanh lam hoặc xám và nó sẽ hoạt động


0

XCode Vesion: 9.4.1, Swift 4.1

Trong trường hợp của tôi, điều này xảy ra, khi tôi chạm vào ô và di chuyển sang chế độ xem khác. Tôi gỡ lỗi vào sâu hơn và có vẻ như điều đó xảy ra bên trong viewDidAppearvì chứa mã sau

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

sau đó tôi đã thêm đoạn mã ở trên vào bên trong prepare(for segue: UIStoryboardSegue, sender: Any?) và hoạt động hoàn hảo.

Theo kinh nghiệm của tôi, giải pháp của tôi là, Nếu chúng tôi hy vọng thực hiện bất kỳ thay đổi mới nào (ví dụ: tải lại bảng, bỏ chọn ô đã chọn, v.v.) cho chế độ xem bảng khi quay lại từ chế độ xem thứ hai, thì hãy sử dụng ủy quyền thay vì viewDidAppearvà sử dụng tableView.deselectRowmã trên phân đoạn trước khi di chuyển bộ điều khiển chế độ xem thứ hai

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.