Câu hỏi modal Xem bộ điều khiển - cách hiển thị và loại bỏ


Tôi đang phá vỡ đầu của tôi trong một tuần cuối cùng về cách giải quyết vấn đề bằng cách hiển thị và loại bỏ nhiều bộ điều khiển chế độ xem. Tôi đã tạo một dự án mẫu và dán mã trực tiếp từ dự án. Tôi có 3 bộ điều khiển xem với các tệp .xib tương ứng của chúng. MainViewController, VC1 và VC2. Tôi có hai nút trên bộ điều khiển xem chính.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

Điều này mở VC1 mà không có vấn đề gì. Trong VC1, tôi có một nút khác sẽ mở VC2 trong khi đồng thời loại bỏ VC1.

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

Tôi muốn nó quay trở lại bộ điều khiển xem chính trong khi đồng thời VC1 nên đã được loại bỏ khỏi bộ nhớ cho tốt. VC1 chỉ hiển thị khi tôi bấm vào nút VC1 trên bộ điều khiển chính.

Các nút khác trên bộ điều khiển xem chính cũng sẽ có thể hiển thị VC2 trực tiếp bỏ qua VC1 và nên quay trở lại bộ điều khiển chính khi một nút được nhấp vào VC2. Không có mã chạy dài, vòng lặp hoặc bất kỳ bộ hẹn giờ nào. Chỉ cần gọi xương trần để xem bộ điều khiển.


76
2018-02-16 06:06


gốc




Các câu trả lời:


Đường thẳng này:

[self dismissViewControllerAnimated:YES completion:nil];

không gửi một tin nhắn cho chính nó, nó thực sự đang gửi một tin nhắn đến VC đang trình bày của nó, yêu cầu nó làm việc sa thải. Khi bạn trình bày một VC, bạn tạo ra một mối quan hệ giữa VC trình bày và VC được trình bày. Vì vậy, bạn không nên phá hủy VC trình bày trong khi nó được trình bày (VC trình bày không thể gửi tin nhắn sa thải trở lại ...). Khi bạn không thực sự tính đến nó, bạn sẽ rời khỏi ứng dụng trong trạng thái bối rối. Xem câu trả lời của tôi Loại bỏ Trình điều khiển Chế độ xem được Trình bày trong đó tôi đề nghị phương pháp này được viết rõ ràng hơn:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

Trong trường hợp của bạn, bạn cần phải đảm bảo rằng tất cả các điều khiển được thực hiện trong mainVC. Bạn nên sử dụng một đại biểu để gửi thông điệp chính xác trở lại MainViewController từ ViewController1, để mainVC có thể loại bỏ VC1 và sau đó trình bày VC2.

Trong VC2 VC1 thêm một giao thức trong tệp .h của bạn phía trên @interface:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

và hạ thấp xuống trong cùng một tệp trong phần @interface khai báo thuộc tính để giữ con trỏ ủy nhiệm:

@property (nonatomic,weak) id <ViewController1Protocol> delegate;

Trong tệp VC1 .m, phương thức bỏ qua nút nên gọi phương thức ủy nhiệm

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

Bây giờ trong mainVC, đặt nó làm đại biểu của VC1 khi tạo VC1:

- (IBAction)present1:(id)sender {
    ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    vc.delegate = self;
    [self present:vc];
}

và thực hiện phương thức ủy nhiệm:

- (void)dismissAndPresent2 {
    [self dismissViewControllerAnimated:NO completion:^{
        [self present2:nil];
    }];
}

present2: có thể là phương pháp tương tự như của bạn VC2Pressed: nút Phương thức IBAction. Lưu ý rằng nó được gọi từ khối hoàn thành để đảm bảo rằng VC2 không được trình bày cho đến khi VC1 bị loại bỏ hoàn toàn.

Bây giờ bạn đang chuyển từ VC1-> VCMain-> VC2, do đó bạn có thể sẽ chỉ muốn một trong những chuyển tiếp được làm động.

cập nhật 

Trong ý kiến ​​của bạn, bạn thể hiện sự ngạc nhiên về sự phức tạp cần thiết để đạt được một điều đơn giản dường như. Tôi đảm bảo với bạn, mô hình ủy quyền này rất tập trung vào phần lớn Objective-C và Cocoa, và ví dụ này là về đơn giản nhất bạn có thể nhận được, rằng bạn thực sự cần nỗ lực để làm quen với nó.

Trong Apple Xem hướng dẫn lập trình điều khiển họ có điều này để nói:

Loại bỏ Trình điều khiển Chế độ xem được Trình bày

Khi nói đến thời gian để loại bỏ một bộ điều khiển xem trình bày, cách tiếp cận ưa thích là để cho trình điều khiển xem trình bày loại bỏ nó. Nói cách khác, bất cứ khi nào có thể, cùng một bộ điều khiển xem trình bày bộ điều khiển xem cũng phải chịu trách nhiệm cho việc loại bỏ nó. Mặc dù có một số kỹ thuật để thông báo cho trình điều khiển xem trình bày mà trình điều khiển xem được trình bày của nó nên được loại bỏ, kỹ thuật ưu tiên là ủy nhiệm. Để biết thêm thông tin, hãy xem "Sử dụng ủy quyền để giao tiếp với các bộ điều khiển khác".

Nếu bạn thực sự nghĩ qua những gì bạn muốn đạt được, và cách bạn đang thực hiện nó, bạn sẽ nhận ra rằng việc nhắn tin MainViewController của bạn để làm tất cả công việc là cách hợp lý duy nhất mà bạn không muốn sử dụng một NavigationController. nếu bạn làm sử dụng một NavController, có hiệu lực bạn đang "ủy nhiệm", ngay cả khi không rõ ràng, để navController để làm tất cả các công việc. Cần phải có một số đối tượng giữ một bản nhạc trung tâm về những gì đang xảy ra với điều hướng VC của bạn và bạn cần một số phương pháp giao tiếp với nó, bất kể bạn làm gì.

Trong thực tế lời khuyên của Apple là một chút cực đoan ... trong trường hợp bình thường, bạn không cần phải thực hiện một đại biểu và phương pháp chuyên dụng, bạn có thể dựa vào [self presentingViewController] dismissViewControllerAnimated: - đó là khi trong các trường hợp như của bạn mà bạn muốn loại bỏ của bạn để có các hiệu ứng khác trên các đối tượng từ xa mà bạn cần phải chăm sóc.

Đây là thứ bạn có thể tưởng tượng để làm việc mà không có tất cả các rắc rối đại biểu ...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

Sau khi yêu cầu trình điều khiển trình bày loại bỏ chúng ta, chúng ta có một khối hoàn thành gọi một phương thức trong presentingViewController để gọi VC2. Không có đại biểu cần thiết. (Một điểm bán hàng lớn của khối là họ giảm nhu cầu cho các đại biểu trong những trường hợp này). Tuy nhiên trong trường hợp này, có một vài điều cản trở ...

  • trong VC1 bạn không biết mainVC thực hiện phương thức present2 - bạn có thể kết thúc với các lỗi hoặc sự cố khó gỡ lỗi. Các đại biểu giúp bạn tránh điều này.
  • một khi VC1 bị loại bỏ, nó không thực sự xung quanh để thực hiện khối hoàn thành ... hay là nó? Liệu self.presentingViewController có ý nghĩa gì nữa không? Bạn không biết (không phải tôi) ... với một đại biểu, bạn không có sự không chắc chắn này.
  • Khi tôi cố gắng chạy phương pháp này, nó chỉ treo mà không có cảnh báo hoặc lỗi.

Vì vậy, xin vui lòng ... dành thời gian để tìm hiểu đoàn!

update2

Trong bình luận của bạn, bạn đã quản lý để làm cho nó hoạt động bằng cách sử dụng điều này trong xử lý nút bỏ qua của VC2:

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

Điều này chắc chắn đơn giản hơn nhiều, nhưng nó khiến bạn gặp phải một số vấn đề.

Khớp nối chặt chẽ
Bạn đang cứng dây của bạn xemController cấu trúc với nhau. Ví dụ, nếu bạn đã chèn một viewController mới trước mainVC, hành vi bắt buộc của bạn sẽ bị phá vỡ (bạn sẽ điều hướng đến hành vi trước đó). Trong VC1 bạn cũng đã phải # import VC2. Vì vậy, bạn có khá nhiều phụ thuộc lẫn nhau, phá vỡ các mục tiêu OOP / MVC.

Sử dụng các đại biểu, cả VC1 lẫn VC2 đều không cần biết bất cứ điều gì về mainVC hay các tiền đề của nó, vì vậy chúng tôi giữ cho mọi thứ được kết nối lỏng lẻo và mô-đun.

Ký ức
VC1 đã không biến mất, bạn vẫn giữ hai con trỏ tới nó:

  • mainVC's presentedViewController bất động sản
  • VC2's presentingViewController bất động sản

Bạn có thể kiểm tra điều này bằng cách đăng nhập và cũng chỉ bằng cách thực hiện việc này từ VC2

[self dismissViewControllerAnimated:YES completion:nil]; 

Nó vẫn hoạt động, vẫn giúp bạn quay lại VC1.

Điều đó dường như với tôi như một rò rỉ bộ nhớ.

Các đầu mối cho điều này là trong cảnh báo bạn đang nhận được ở đây:

[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
 // Attempt to dismiss from view controller <VC1: 0x715e460>
 // while a presentation or dismiss is in progress!

Logic phân tích, khi bạn đang cố gắng loại bỏ VC trình bày trong đó VC2 là VC được trình bày. Thông điệp thứ hai không thực sự được thực hiện - có lẽ một số thứ sẽ xảy ra, nhưng bạn vẫn còn lại với hai con trỏ tới một đối tượng mà bạn nghĩ bạn đã loại bỏ. (chỉnh sửa - Tôi đã kiểm tra điều này và nó không quá tệ, cả hai đối tượng đều biến mất khi bạn quay lại mainVC)

Đó là một cách nói khá dài - xin hãy sử dụng các đại biểu. Nếu nó giúp, tôi đã thực hiện một mô tả ngắn gọn về mô hình ở đây:
Có phải là đi qua một bộ điều khiển trong một construtor luôn luôn là một thực tế xấu?

cập nhật 3
Nếu bạn thực sự muốn tránh các đại biểu, đây có thể là cách tốt nhất:

Trong VC1:

[self presentViewController:VC2
                   animated:YES
                 completion:nil];

Nhưng không loại bỏ bất cứ điều gì ... như chúng tôi xác định chắc chắn, nó không thực sự xảy ra anyway.

Trong VC2:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

Như chúng ta (biết), chúng tôi đã không loại bỏ VC1, chúng tôi có thể quay lại xuyên qua VC1 đến MainVC. MainVC loại bỏ VC1. Bởi vì VC1 đã đi, nó được trình bày VC2 đi với nó, vì vậy bạn đang trở lại tại MainVC trong trạng thái sạch.

Nó vẫn còn rất cao, như VC1 cần biết về VC2, và VC2 cần biết rằng nó đã được chuyển qua MainVC-> VC1, nhưng đó là điều tốt nhất bạn sẽ nhận được mà không có một phái đoàn rõ ràng nào.


184
2018-02-16 12:49



có vẻ phức tạp. Tôi đã cố gắng theo dõi và sao chép vào dấu chấm nhưng bị lạc ở giữa. Có cách nào khác để đạt được điều này không ?. Tôi cũng muốn thêm điều đó vào trong ủy nhiệm ứng dụng, bộ điều khiển chính được đặt làm bộ điều khiển chế độ xem gốc. Tôi không muốn sử dụng bộ điều khiển Danh mục chính nhưng tự hỏi tại sao điều này lại quá phức tạp để đạt được. Để tóm tắt, khi ứng dụng bắt đầu, tôi hiển thị một bộ điều khiển xem chính với 2 nút. Nhấp vào nút đầu tiên tải VC1. Có một nút trên VC1 và khi nhấp vào nó sẽ tải VC2 không có lỗi hoặc cảnh báo trong khi đồng thời loại bỏ VC1 khỏi bộ nhớ. - Hema
Trên VC2, tôi có một nút bấm và nó sẽ loại bỏ VC2 khỏi bộ nhớ và điều khiển sẽ quay trở lại bộ điều khiển chính và không đi đến VC1. - Hema
@ Hema, tôi đã hiểu rõ các yêu cầu của bạn và đảm bảo với bạn điều này Là cách chính xác để làm điều đó. Tôi đã cập nhật câu trả lời của mình với một chút thông tin, hy vọng điều đó sẽ giúp ích. Nếu bạn đã thử cách tiếp cận của tôi và gặp khó khăn, hãy nêu ra một câu hỏi mới cho thấy chính xác những gì không hoạt động để chúng tôi có thể trợ giúp. Bạn cũng có thể liên kết ngược lại câu hỏi này để rõ ràng. - foundry
Hi He Was: Cảm ơn sự hiểu biết của bạn. Tôi cũng đang nói về một chủ đề khác (chủ đề ban đầu) và chỉ đăng một đoạn trích từ các đề xuất được đề cập ở đó. Tôi đang thử tất cả các câu trả lời của chuyên gia để giải quyết vấn đề này. URL ở đây: stackoverflow.com/questions/14840318/… - Hema
@ Honey - Có lẽ như vậy, nhưng tuyên bố là một câu trả lời hùng biện cho một mảnh 'giả tưởng' giả. Điểm mà tôi muốn tạo ra không phải là về việc giữ lại các bẫy chu kỳ, mà là để giáo dục người hỏi là tại sao phái đoàn lại là một mẫu thiết kế có giá trị (điều này vô tình tránh được vấn đề đó). Tôi nghĩ rằng đó là sự tranh cãi gây hiểu lầm ở đây - câu hỏi là về các phương thức VC, nhưng giá trị của câu trả lời nằm chủ yếu trong việc giải thích mẫu đại biểu, sử dụng câu hỏi, và sự thất vọng rõ ràng của OP, như chất xúc tác. Cảm ơn bạn đã quan tâm (và chỉnh sửa của bạn) !! - foundry


Tôi nghĩ rằng bạn hiểu lầm một số khái niệm cốt lõi về bộ điều khiển chế độ xem iOS. Khi bạn loại bỏ VC1, bất kỳ bộ điều khiển xem được trình bày nào của VC1 cũng sẽ bị loại bỏ. Apple dự định cho các bộ điều khiển xem phương thức theo một cách xếp chồng - trong trường hợp của bạn, VC2 được trình bày bởi VC1. Bạn đang loại bỏ VC1 ngay sau khi bạn giới thiệu VC2 từ VC1 vì vậy nó là một mớ hỗn độn. Để đạt được những gì bạn muốn, buttonPressedFromVC1 nên có mainVC hiện tại VC2 ngay sau khi VC1 loại bỏ chính nó. Và tôi nghĩ điều này có thể đạt được nếu không có đại biểu. Một cái gì đó dọc theo dòng:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

Lưu ý rằng self.presentingViewController được lưu trữ trong một số biến khác, bởi vì sau khi vc1 loại bỏ chính nó, bạn không nên thực hiện bất kỳ tham chiếu đến nó.


9
2018-02-25 14:59



Quá dễ! tôi muốn những người khác sẽ cuộn xuống câu trả lời của bạn thay vì dừng lại ở bài đăng hàng đầu. - Ryan Loggerythm
trong mã của OP, tại sao không [self dismiss...] xảy ra sau  [self present...] đã hoàn thành? Nó không có cái gì đó không đồng bộ xảy ra - Honey
@ Thực sự, có cái gì đó không đồng bộ xảy ra khi gọi presentViewController - đó là lý do tại sao nó có một trình xử lý hoàn thành. Nhưng ngay cả khi sử dụng, nếu bạn loại bỏ bộ điều khiển hiển thị xem sau khi nó trình bày một cái gì đó, tất cả mọi thứ mà nó trình bày được miễn nhiệm là tốt. Vì vậy, OP thực sự muốn trình bày bộ điều khiển xem từ một người trình bày khác thực sự, để nó có thể loại bỏ bộ điều khiển hiện tại - Radu Simionescu
Nhưng ngay cả khi sử dụng, nếu bạn bỏ qua trình điều khiển xem trình bày sau khi nó trình bày một cái gì đó, mọi thứ mà nó trình bày cũng bị loại bỏ... Aha, do đó trình biên dịch về cơ bản nói "những gì bạn làm là ngu ngốc. Bạn chỉ cần hoàn tác dòng mã trước đó của bạn (vì VC1 tôi sẽ loại bỏ bản thân mình và bất cứ điều gì tôi đang trình bày). Đừng làm điều đó" đúng? - Honey
Trình biên dịch sẽ không "nói" bất cứ điều gì về nó, và nó cũng có thể là trường hợp không sụp đổ khi thực hiện điều này, chỉ là nó sẽ hành xử theo cách mà lập trình viên không mong đợi - Radu Simionescu


Ví dụ trong Nhanh, hình dung lời giải thích của xưởng đúc ở trên và tài liệu của Apple:

  1. Dựa trên Tài liệu của Apple và xưởng đúc giải thích ở trên (sửa một số lỗi), presentViewController phiên bản sử dụng mẫu thiết kế đại biểu:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func dismissViewController1AndPresentViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
        self.presentViewController(vc1, animated: true, completion: nil)
    }

    func dismissViewController1AndPresentViewController2() {
        self.dismissViewControllerAnimated(false, completion: { () -> Void in
            let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
            self.presentViewController(vc2, animated: true, completion: nil)
        })
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
  1. Dựa trên lời giải thích của xưởng đúc ở trên (sửa một số lỗi), phiên bản pushViewController sử dụng mẫu thiết kế ủy nhiệm:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func popViewController1AndPushViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        self.navigationController?.pushViewController(vc1, animated: true)
    }

    func popViewController1AndPushViewController2() {
        self.navigationController?.popViewControllerAnimated(false)
        let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
        self.navigationController?.pushViewController(vc2, animated: true)
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}

8
2017-12-12 01:21



trong ví dụ của bạn ViewController class là mainVC phải không? - Honey


Radu Simionescu - công việc tuyệt vời! và bên dưới Giải pháp của bạn cho những người yêu thích Swift:

@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
    let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
    var presentingVC = self.presentingViewController
    self.dismissViewControllerAnimated(false, completion: { () -> Void   in
        presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
    })
}

4
2018-04-16 10:14



điều này theo cách khiến tôi thất vọng vì nó thực sự hoạt động .. Tôi không hiểu tại sao khối không chụp "self.presentingViewController" và một tham chiếu mạnh là cần thiết, tức là "var presentingVC" .. anyways, công trình này. cám ơn - emdog4


Tôi muốn điều này:

MapVC là một bản đồ ở chế độ toàn màn hình.

Khi tôi nhấn một nút, nó sẽ mở ra PopupVC (không phải toàn màn hình) phía trên bản đồ.

Khi tôi nhấn một nút trong PopupVC, nó trở về MapVC, và sau đó tôi muốn thực thi viewDidAppear.

Tôi đã làm điều này:

MapVC.m: trong tác vụ nút, phân đoạn theo lập trình và đặt đại biểu

- (void) buttonMapAction{
   PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
   popvc.delegate = self;
   [self presentViewController:popvc animated:YES completion:nil];
}

- (void)dismissAndPresentMap {
  [self dismissViewControllerAnimated:NO completion:^{
    NSLog(@"dismissAndPresentMap");
    //When returns of the other view I call viewDidAppear but you can call to other functions
    [self viewDidAppear:YES];
  }];
}

PopupVC.h: trước @interface, thêm giao thức

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

sau @interface, một thuộc tính mới

@property (nonatomic,weak) id <PopupVCProtocol> delegate;

PopupVC.m:

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}

0
2017-08-31 11:47





Tôi đã giải quyết vấn đề bằng cách sử dụng UINavigationController khi trình bày. Trong MainVC, khi trình bày VC1

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

Trong VC1, khi tôi muốn thể hiện VC2 và loại bỏ VC1 trong cùng một thời điểm (chỉ một hoạt ảnh), tôi có thể có hoạt ảnh đẩy bằng

let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)

Và trong VC2, khi đóng bộ điều khiển xem, như thường lệ, chúng ta có thể sử dụng:

self.dismiss(animated: true, completion: nil)

0
2017-11-02 17:21