presentViewController: ภาพเคลื่อนไหว: มุมมองใช่จะไม่ปรากฏจนกว่าผู้ใช้จะแตะอีกครั้ง


93

presentViewController:animated:completionฉันได้รับบางพฤติกรรมแปลกกับ สิ่งที่ฉันกำลังทำคือเกมทายใจ

ฉันมีUIViewController(frequencyViewController) ที่มีUITableView(frequencyTableView) เมื่อผู้ใช้แตะที่แถวที่มีปัญหา TableView ที่มีคำตอบที่ถูกต้องมุมมอง (correctViewController) ควรเป็นอินสแตนซ์และมุมมองควรเลื่อนขึ้นจากด้านล่างของหน้าจอเป็นมุมมองแบบโมดอล สิ่งนี้จะบอกผู้ใช้ว่าพวกเขามีคำตอบที่ถูกต้องและรีเซ็ต frequencyViewController ข้างหลังพร้อมสำหรับคำถามต่อไป correctViewController ถูกปิดเมื่อกดปุ่มเพื่อเปิดเผยคำถามถัดไป

ทั้งหมดนี้ทำงานอย่างถูกต้องทุกครั้งและมุมมองของ correctViewController ปรากฏขึ้นทันทีตราบใดที่มีpresentViewController:animated:completionanimated:NO

หากฉันตั้งanimated:YES, correctViewController viewDidLoadจะเริ่มต้นและทำให้การโทรไปยัง อย่างไรก็ตามviewWillAppear, viewDidAppearและบล็อกเสร็จสิ้นจากการpresentViewController:animated:completionไม่ได้เรียกว่า แอปเพิ่งตั้งอยู่ที่นั่นโดยยังคงแสดง frequencyViewController จนกว่าฉันจะแตะครั้งที่สอง ตอนนี้ viewWillAppear, viewDidAppear และบล็อกเสร็จสมบูรณ์ถูกเรียก

ฉันตรวจสอบอีกเล็กน้อยและไม่ใช่แค่การแตะอีกครั้งที่จะทำให้มันดำเนินต่อไป ดูเหมือนว่าถ้าฉันเอียงหรือเขย่า iPhone ของฉันสิ่งนี้อาจทำให้เกิดการเรียกใช้ viewWillLoad เป็นต้นเหมือนกับว่ามันกำลังรอให้ผู้ใช้ป้อนข้อมูลอื่น ๆ ก่อนที่มันจะดำเนิน สิ่งนี้เกิดขึ้นบน iPhone จริงและในเครื่องจำลองซึ่งฉันพิสูจน์แล้วโดยส่งคำสั่งเขย่าไปยังเครื่องจำลอง

ฉันรู้สึกสูญเสียจริงๆกับสิ่งที่ต้องทำเกี่ยวกับเรื่องนี้ ... ฉันขอขอบคุณสำหรับความช่วยเหลือที่ทุกคนสามารถให้ได้

ขอบคุณ

นี่คือรหัสของฉัน ค่อนข้างเรียบง่าย ...

นี่คือโค้ดใน questionViewController ที่ทำหน้าที่เป็นผู้รับมอบสิทธิ์ให้กับ 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];
        }];
    }
}

นี่คือทั้งหมดของ 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
ธีโอ

ผมมีปัญหาเดียวกัน. ดูเหมือนจะเป็นข้อบกพร่องของ iOS7 - ไม่เกิดขึ้นบน iOS6 นอกจากนี้ในกรณีของฉันมันเกิดขึ้นเพียงครั้งแรกหลังจากที่ฉันเปิดตัวควบคุมมุมมองการนำเสนอ @ คุณช่วยส่งลิงค์ไปยังฟอรัม Apple Dev ได้ไหม
AX

คำตอบ:


158

ฉันพบปัญหาเดียวกันในวันนี้ ฉันเจาะเข้าไปในหัวข้อและดูเหมือนว่ามันเกี่ยวข้องกับ runloop หลักที่กำลังหลับอยู่

จริงๆแล้วมันเป็นข้อบกพร่องที่ละเอียดอ่อนมากเพราะหากคุณมีภาพเคลื่อนไหวข้อเสนอแนะตัวจับเวลา ฯลฯ ในโค้ดน้อยที่สุดปัญหานี้จะไม่ปรากฏเนื่องจากแหล่งข้อมูลเหล่านี้จะยังคงมีชีวิตอยู่ ฉันพบปัญหาโดยใช้สิ่งUITableViewCellที่มีการselectionStyleตั้งค่าเป็นUITableViewCellSelectionStyleNoneเพื่อให้ไม่มีภาพเคลื่อนไหวการเลือกทริกเกอร์รันลูปหลังจากที่ตัวจัดการการเลือกแถวทำงาน

ในการแก้ไข (จนกว่า Apple จะทำบางอย่าง) คุณสามารถเรียกใช้ runloop หลักได้หลายวิธี:

วิธีแก้ปัญหาที่รบกวนน้อยที่สุดคือโทร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
ฉันมีปัญหาเดียวกันแน่นอนว่าน่ารำคาญจริงๆโชคดีที่สิ่งนี้ได้รับการแก้ไข
ทำเครื่องหมาย

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
ฉันใช้ iOS 8 และสิ่งนี้ไม่สามารถแก้ไขได้สำหรับฉัน - มันมักจะรายงานว่าอยู่ในเธรดหลัก แต่ฉันยังคงเห็นปัญหาที่อธิบายไว้
Robert

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
Robert

0

ฉันอยากรู้อยากเห็นว่า[self setUpViewForNextQuestion];ทำอย่างไร

คุณอาจจะลองโทรในตอนท้ายของบล็อกเสร็จคุณใน[self.correctViewController.view setNeedsDisplay];presentViewController


ตอนนี้ setUpViewForNextQuestion เรียกใช้ reloadData บน tableview (เพื่อเปลี่ยนสีพื้นหลังทั้งหมดกลับเป็นสีขาวหากมีการตั้งค่าเป็นสีแดงโดยการเดาไม่ถูกต้อง) แต่การเปลี่ยนแปลงใด ๆ จะสร้างความแตกต่างหรือไม่? ปัญหาคือ rightViewController จะไม่ปรากฏจนกว่าจะมีการแตะการใช้งานอีกครั้งดังนั้นจึงไม่มีการเรียกบล็อกการเสร็จสิ้นนี้จนกว่าจะแตะครั้งที่สอง
HalfNormalled

ฉันลองทำตามคำแนะนำของคุณแล้ว แต่ก็ไม่ได้สร้างความแตกต่าง อย่างที่ฉันบอกว่ามุมมองไม่ปรากฏขึ้นดังนั้นจึงไม่มีการเรียกบล็อกที่สมบูรณ์จนกว่าจะแตะครั้งที่สองหรือท่าทางสั่น
HalfNormalled

อืม. สำหรับผู้เริ่มฉันจะใช้เบรกพอยต์เพื่อยืนยันว่ารหัสการนำเสนอของคุณไม่ได้ถูกเรียกจนกว่าจะแตะครั้งที่ 2 (เทียบกับการถูกเรียกในการแตะครั้งแรก แต่จะไม่ถูกดึงออกมาบนหน้าจอจนกว่าจะถึงเวลาต่อมา) ถ้าหลังเป็นจริงลองบังคับให้งานนำเสนอของคุณปรากฏบนเธรดหลัก เมื่อคุณใช้ "performSelectorWithDelay: 0" ที่บังคับให้แอปรอจนกว่าจะมีการวนซ้ำครั้งถัดไปของการวนรอบการทำงานก่อนที่จะดำเนินการ อาจเกี่ยวข้องกับเธรด
นิค

ขอบคุณนิค ฉันจะตรวจสอบโดยใช้เบรกพอยต์ได้อย่างไร ในขณะนี้ฉันกำลังใช้ NSLog เพื่อตรวจสอบว่าแต่ละวิธีถูกเรียกใช้เมื่อใด นี่คือสิ่งที่ฉันมีตอนนี้: แตะ 1 Correct Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] ไม่มีอะไรเกิดขึ้นจนกว่า: แตะ 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]
ครึ่งหนึ่งปกติ

ขออภัยควรมีความชัดเจนมากกว่านี้ ฉันเข้าใจวิธีใช้เบรกพอยต์ ฉันได้รับเรื่องราวเช่นเดียวกับ NSLog ฉันใส่เบรกพอยต์บน viewDidLoad และ viewDidAppear ใน correctViewController มันแตกบน viewDidLoad จากนั้นเมื่อฉันดำเนินการต่อมันจะนั่งและรอการแตะอีกครั้งจากนั้นจะหยุดพักบน viewDidAppear บางครั้งดูเหมือนว่าไม่จำเป็นต้องใช้การแตะและถึงเวลาตามเวลาที่ฉันก้าวผ่านสิ่งต่างๆเพื่อดูว่ามีอะไรอีกบ้างที่เรียกว่า viewDidLoad
HalfNormalled

0

ฉันได้เขียนส่วนขยาย (หมวดหมู่) ด้วยวิธีการสลับสำหรับ UIViewController ที่แก้ปัญหาได้ ขอบคุณ AX และ NSHipster สำหรับคำแนะนำการใช้งาน ( รวดเร็ว / วัตถุประสงค์ -c )

รวดเร็ว

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?)และทำงานได้อย่างสมบูรณ์แบบ

จากประสบการณ์ของฉันวิธีแก้ปัญหาของฉันคือถ้าเราหวังว่าจะทำการเปลี่ยนแปลงใหม่ ๆ (เช่นการโหลดตารางใหม่ยกเลิกการเลือกเซลล์ที่เลือก ฯลฯ ) สำหรับ tableview เมื่อกลับมาจากมุมมองที่สองอีกครั้งจากนั้นใช้ delegate แทนviewDidAppearและใช้tableView.deselectRowโค้ดด้านบนส่วนก่อนที่จะย้ายตัวควบคุมมุมมองที่สอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.