Trang tính phát hiện đã bị loại bỏ trên iOS 13


115

Trước iOS 13, các bộ điều khiển chế độ xem được trình bày được sử dụng để bao phủ toàn bộ màn hình. Và, khi bị loại bỏ, viewDidAppearchức năng bộ điều khiển chế độ xem chính được thực thi.

Giờ đây, iOS 13 sẽ hiển thị bộ điều khiển chế độ xem dưới dạng trang tính theo mặc định, có nghĩa là thẻ sẽ che một phần bộ điều khiển chế độ xem bên dưới, có nghĩa là nó viewDidAppearsẽ không được gọi, vì bộ điều khiển chế độ xem chính chưa bao giờ biến mất.

Có cách nào để phát hiện rằng trang điều khiển chế độ xem được trình bày đã bị loại bỏ không? Một số chức năng khác mà tôi có thể ghi đè trong bộ điều khiển chế độ xem mẹ thay vì sử dụng một số loại đại biểu ?


5
Đã được thảo luận kỹ trong developer.apple.com/videos/play/wwdc2019/224
matt.

Vì vậy, có cách nào để loại bỏ tất cả các trang tính cùng một lúc vào vc gốc?
Weslie,


Tại sao bạn cần biết khi nó bị loại bỏ? Nếu đó là để tải lại dữ liệu và cập nhật giao diện người dùng, Thông báo hoặc KVO có thể là một lựa chọn thay thế tốt.
martn_st

Câu trả lời:


58

Có cách nào để phát hiện rằng trang điều khiển chế độ xem được trình bày đã bị loại bỏ không?

Đúng.

Một số chức năng khác mà tôi có thể ghi đè trong bộ điều khiển chế độ xem mẹ thay vì sử dụng một số loại đại biểu?

Không. "Một số loại đại biểu" là cách bạn làm điều đó. Tự đặt mình làm đại biểu của người điều khiển bản trình bày và ghi đè presentationControllerDidDismiss(_:).

https://developer.apple.com/documentation/uikit/uiadaptivepresentationcontrollerdelegate/3229889-presentationcontrollerdiddismiss


Việc thiếu sự kiện tạo thời gian chạy chung thông báo cho bạn biết rằng bộ điều khiển chế độ xem được trình bày, cho dù toàn màn hình hay không, đã bị loại bỏ, thực sự rất phiền phức; nhưng nó không phải là một vấn đề mới, bởi vì luôn có những bộ điều khiển chế độ xem được trình bày không toàn màn hình. Chỉ là bây giờ (trong iOS 13) có nhiều thứ hơn! Tôi dành một câu hỏi và câu trả lời riêng cho chủ đề này ở nơi khác: Phát hiện UIViewController hợp nhất "trở thành hàng đầu"? .


6
điều này là không đủ. Nếu bạn có một thanh điều hướng trong VC được trình bày của mình và một nút thanh tùy chỉnh loại bỏ chế độ xem của bạn theo chương trình, thì bộ điều khiển bản trình bày đã loại bỏ sẽ không được gọi.
Irina

14
Xin chào @Irina - nếu bạn loại bỏ chế độ xem của mình theo chương trình, bạn không cần gọi lại vì bạn đã loại bỏ chế độ xem của mình theo lập trình - bạn biết bạn đã làm điều đó vì bạn đã làm điều đó. Phương thức ủy quyền chỉ trong trường hợp người dùng thực hiện nó.
matt

6
@matt Cảm ơn vì câu trả lời. Khi chế độ xem bị loại bỏ theo chương trình, điều này sẽ không được gọi (như Irina nói) và bạn nói đúng rằng chúng tôi biết chúng tôi đã làm điều đó. Tôi chỉ nghĩ rằng có một số lượng mã soạn sẵn không cần thiết để viết chỉ để có được một loại 'viewWillAppear' với phong cách trình bày phương thức mới trong iOS13. Nó được đặc biệt lộn xộn khi bạn đang quản lý định tuyến thông qua một kiến trúc nơi định tuyến được chiết xuất (trong MVVM + điều phối viên, hoặc một loại router trong VIPER ví dụ)
Adam Waite

3
@AdamWaite Tôi đồng ý nhưng vấn đề này không mới. Chúng tôi đã gặp vấn đề này trong nhiều năm, với các cửa sổ bật lên, với các bộ điều khiển chế độ xem được trình bày không toàn màn hình, với các cảnh báo, v.v. Tôi coi đây là một lỗ hổng nghiêm trọng trong kho "sự kiện" của Apple. Tôi chỉ nói thực tế là gì và tại sao. Tôi đối mặt trực tiếp với vấn đề tại đây: stackoverflow.com/questions/54602662/…
matt

1
PresentationControllerDidDismiss (_ :). không được gọi khi tôi nhấp vào nút quay lại trong VC Con. Bất kỳ giúp đỡ?
Krishna Meena

41

Dưới đây là ví dụ mã của bộ điều khiển chế độ xem chính được thông báo khi bộ điều khiển chế độ xem con mà nó hiển thị dưới dạng trang tính (tức là theo cách mặc định của iOS 13) bị loại bỏ:

public final class Parent: UIViewController, UIAdaptivePresentationControllerDelegate
{
  // This is assuming that the segue is a storyboard segue; 
  // if you're manually presenting, just see the delegate there.
  public override func prepare(for segue: UIStoryboardSegue, sender: Any?)
  {
    if segue.identifier == "mySegue" {
      segue.destination.presentationController?.delegate = self;
    }
  }

  public func presentationControllerDidDismiss(
    _ presentationController: UIPresentationController)
  {
    // Only called when the sheet is dismissed by DRAGGING.
    // You'll need something extra if you call .dismiss() on the child.
    // (I found that overriding dismiss in the child and calling
    // presentationController.delegate?.presentationControllerDidDismiss
    // works well).
  }
}

Câu trả lời của Jerland2 bị nhầm lẫn, vì (a) người hỏi ban đầu muốn nhận một lệnh gọi hàm khi trang tính bị loại bỏ (trong khi anh ta triển khai PresentationControllerDidAttemptToDismiss, được gọi khi người dùng cố gắng và không thể loại bỏ trang tính) và (b) cài đặt isModalInPresentation là hoàn toàn trực giao và trên thực tế sẽ làm cho trang tính được trình bày không thể bỏ qua (điều này ngược lại với những gì OP muốn).


6
Điều này hoạt động tốt. Chỉ một mẹo nhỏ là nếu bạn sử dụng bộ điều khiển nav trên VC được gọi của mình, bạn nên gán bộ điều khiển điều khiển nav làm PresentationController ?, ủy nhiệm (không phải VC mà nav có là topViewController).
instAustralia

@instAustralia bạn có thể giải thích lý do tại sao hoặc tham khảo tài liệu không? Cảm ơn.
Ahmed Osama

PresentationControllerDidDismiss Làm thế nào để gọi nó khi người dùng nhấn nút quay lại?
Krishna Meena

@AhmedOsama - bộ điều khiển điều hướng là bộ điều khiển bản trình bày và do đó là người được ủy quyền vì nó sẽ là người phản hồi việc sa thải. Tôi cũng đã thử VC được nhúng trong Bộ điều khiển điều hướng nhưng đây là nơi các nút thực tế của tôi để loại bỏ tồn tại và phản hồi. Tôi không thể tìm thấy nó trực tiếp trong tài liệu của Apple nhưng nó được tham khảo ở đây sarunw.com/posts/modality-changes-in-ios13
instAustralia

26

Một tùy chọn khác để quay lại viewWillAppearviewDidAppearđược thiết lập

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen

tùy chọn này bao phủ toàn màn hình và sau khi loại bỏ, gọi các phương thức trên


2
Cảm ơn PiterPan. Điều này đang hoạt động. Đây là cách giải quyết tuyệt vời và nhanh nhất.
Erkam KUCET

Cảm ơn bạn vì cách nhanh chóng và đáng tin cậy này để khôi phục hành vi mặc định trước đây. Thật tuyệt khi có thể đưa ra bản sửa lỗi này ngay lập tức và sau đó lập kế hoạch chuyển đổi sang hành vi mới một cách hợp lý.
Ian Lovejoy

12
Đây là một giải pháp thay vì sửa chữa. Sẽ không tuyệt vời cho tất cả mọi người nếu chỉ hoàn nguyên về các biểu định kiểu iOS 12. IOS 13 rất tuyệt! :)
Matt

1
hãy cẩn thận khi sử dụng phần mềm này cho iPad, vì iPad được mặc định hiển thị dưới dạng pageSheet khi được trình bày theo phương thức. Điều này sẽ buộc iPad hiển thị dưới dạng toàn màn hình
wyu

không làm việc cho tôi. Tôi mở bộ điều khiển phương thức. đóng nó bằng loại bỏ, nhưng willAppear không được gọi. Tại sao? cảm ơn
neo999

21

Đối với độc giả trong tương lai, đây là một câu trả lời đầy đủ hơn với việc triển khai:

  1. Trong bộ điều khiển chế độ xem gốc, hãy chuẩn bị cho việc xác minh thêm phần sau (Giả sử phương thức của bạn có bộ điều khiển điều hướng)
    // Modal Dismiss iOS 13
    modalNavController.presentationController?.delegate = modalVc
  1. Trong bộ điều khiển chế độ xem phương thức, hãy thêm đại biểu + phương thức sau
// MARK: - iOS 13 Modal (Swipe to Dismiss)

extension ModalViewController: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {


        print("slide to dismiss stopped")
        self.dismiss(animated: true, completion: nil)
    }
}
  1. Đảm bảo trong Bộ điều khiển chế độ xem phương thức rằng thuộc tính sau là đúng để phương thức ủy quyền được gọi
    self.isModalInPresentation = true
  1. Lợi nhuận

1
self.isModalInPresentation = true thì loại bỏ kéo không hoạt động. loại bỏ mà phương thức đại biểu dòng vẫn được gọi là ổn. cảm ơn bạn.
Yogesh Patel

2
Điều này là nhầm lẫn vì (a) người hỏi ban đầu muốn nhận một lệnh gọi hàm khi trang tính bị loại bỏ (trong khi bạn đã triển khai PresentationControllerDidAttemptToDismiss, được gọi khi người dùng cố gắng và không thể loại bỏ trang tính) và (b) cài đặt isModalInPresentation là hoàn toàn trực giao và trên thực tế sẽ làm cho trang tính được trình bày không thể bỏ qua (điều này ngược lại với những gì OP muốn).
Matt,

1
Theo dõi để biết câu trả lời của @Matt (a): Sử dụng presentationControllerDidDismissnên hiệu quả
gondo

5

Nhanh

Giải pháp chung để gọi viewWillAppear trong iOS13

class ViewController: UIViewController {

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            print("viewWillAppear")
        }

        //Show new viewController
        @IBAction func show(_ sender: Any) {
            let newViewController = NewViewController()
            //set delegate of UIAdaptivePresentationControllerDelegate to self
            newViewController.presentationController?.delegate = self
            present(newViewController, animated: true, completion: nil)
        }
    }

    extension UIViewController: UIAdaptivePresentationControllerDelegate {
        public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
            if #available(iOS 13, *) {
                //Call viewWillAppear only in iOS 13
                viewWillAppear(true)
            }
        }
    }

1
Điều này chỉ xử lý loại bỏ bằng cách sử dụng trang trình bày từ trên cùng, không phải bằng cách gọi hàm dismiss(_).
Pedro Paulo Amorim

3

DRAG HOẶC GỌI DISMISS FUNC sẽ hoạt động với mã dưới đây.

1) Trong bộ điều khiển chế độ xem gốc, bạn cho biết đó là bộ điều khiển chế độ xem bản trình bày của nó như mã bên dưới

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "presenterID" {
        let navigationController = segue.destination as! UINavigationController
        if #available(iOS 13.0, *) {
            let controller = navigationController.topViewController as! presentationviewcontroller
            // Modal Dismiss iOS 13
            controller.presentationController?.delegate = self
        } else {
            // Fallback on earlier versions
        }
        navigationController.presentationController?.delegate = self

    }
}

2) Một lần nữa trong bộ điều khiển chế độ xem gốc, bạn cho biết bạn sẽ làm gì khi bộ điều khiển chế độ xem bản trình bày của nó bị mổ xẻ

public func presentationControllerDidDismiss(
  _ presentationController: UIPresentationController)
{
    print("presentationControllerDidDismiss")
}

1) Trong bộ điều khiển dạng xem bản trình bày, Khi bạn nhấn nút hủy hoặc lưu trong hình này. Mã bên dưới sẽ được gọi.

self.dismiss(animated: true) {
        self.presentationController?.delegate?.presentationControllerDidDismiss?(self.presentationController!)
    }

nhập mô tả hình ảnh ở đây


1
có cần thiết phải truyền điều hướngController.topViewController sang PresentationViewController không? Tôi thấy nó không phải
Fitsyu

Làm cách nào để tải lại dữ liệu trong VC mẹ sau khi loại bỏ VC con của nút Hủy?
Krishna Meena

3

Ghi đè viewWillDisappear trên UIViewController đang bị loại bỏ. Nó sẽ cảnh báo bạn về việc sa thải thông qua isBeingDismissedcờ boolean.

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if isBeingDismissed {
        print("user is dismissing the vc")
    }
}

** Nếu người dùng thực hiện được một nửa thao tác vuốt xuống và vuốt thẻ sao lưu, thẻ sẽ vẫn được ghi là bị loại bỏ, ngay cả khi thẻ không bị loại bỏ. Nhưng đó là một trường hợp cạnh mà bạn có thể không quan tâm.


Cònself.dismiss(animated: Bool, completion: (() -> Void)?)
iGhost

0

Nếu ai đó không có quyền truy cập vào trình điều khiển chế độ xem được trình bày, họ chỉ có thể ghi đè phương pháp sau trong trình điều khiển chế độ xem và thay đổi modalPresentationStylethành fullScreenhoặc có thể thêm một trong các chiến lược được đề cập ở trên với cách tiếp cận này

 override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    if let _ = viewControllerToPresent as? TargetVC {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
    }
    super.present(viewControllerToPresent, animated: flag, completion: completion)
}

nếu được trình bày bộ điều khiển chế độ xem là bộ điều khiển điều hướng và bạn muốn kiểm tra bộ điều khiển gốc, có thể thay đổi điều kiện trên để giống như

if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC {
   viewControllerToPresent.modalPresentationStyle = .fullScreen
}

-2

Nếu bạn đã sử dụng ModalPresentationStyle trong FullScreen, hoạt động của bộ điều khiển sẽ trở lại như bình thường.

ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController ("ConsultarController") dưới dạng ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController (controllerConsultar, true);


Lặp lại các câu trả lời hiện có.
matt

-3

Theo quan điểm của tôi, Apple không nên đặt pageSheetlà mặc địnhmodalPresentationStyle

Tôi muốn đưa fullScreenphong cách trở lại mặc định bằng cách sử dụngswizzling

Như thế này:

private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
       let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIViewController {

    static func preventPageSheetPresentationStyle () {
        UIViewController.preventPageSheetPresentation
    }

    static let preventPageSheetPresentation: Void = {
        if #available(iOS 13, *) {
            _swizzling(forClass: UIViewController.self,
                       originalSelector: #selector(present(_: animated: completion:)),
                       swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
        }
    }()

    @available(iOS 13.0, *)
    private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
                                        animated flag: Bool,
                                        completion: (() -> Void)? = nil) {
        if viewControllerToPresent.modalPresentationStyle == .pageSheet
                   || viewControllerToPresent.modalPresentationStyle == .automatic {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        _swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}

Và sau đó đặt dòng này cho AppDelegate

UIViewController.preventPageSheetPresentationStyle()

1
Điều này thật khéo léo nhưng tôi không thể đồng ý với nó. Nó hacky và hơn thế nữa, nó đi ngược lại nguyên lý của iOS 13. Bạn được cho là sử dụng các bản trình bày "thẻ" trong iOS 13. Câu trả lời mà Apple mong đợi từ chúng tôi không phải là "làm việc xung quanh nó"; nó là "vượt qua nó".
matt

Đồng ý với quan điểm của bạn, giải pháp này không giúp ích gì cho việc sử dụng phong cách trình bày thẻ như những gì Apple khuyến khích chúng tôi. Tuy nhiên, thiết lập nó như là phong cách mặc định sẽ làm cho các tuyến đường hiện có mã sai lầm ở đâu đó vì presentingViewControllersẽ không kích hoạtviewWillAppear
jacob

1
Có, nhưng như tôi đã nói trong câu trả lời của riêng mình, đó luôn là một vấn đề đối với các bản trình bày không toàn màn hình (chẳng hạn như cửa sổ bật lên và trang / biểu mẫu trên iPad), vì vậy điều này không có gì mới. Chỉ là bây giờ có nhiều thứ hơn. Dựa vào viewWillAppeartheo một nghĩa nào đó thì luôn luôn sai. Tất nhiên tôi không thích Apple đi cùng và cắt giảm tầng lớp dưới quyền của tôi. Nhưng như tôi nói, chúng ta phải sống với điều đó và làm mọi thứ theo một cách mới.
matt

Trong dự án của tôi, có một số tình huống mà tôi không biết nơi trình điều khiển chế độ xem (được gọi là presentedController) được trình bày và cũng không biết chính xác là cái gì presentingViewController. Ví dụ: trong một số trường hợp, tôi phải sử dụng UIViewController.topMostViewController()nó trả về cho tôi bộ điều khiển chế độ xem nhiều nhất trên cửa sổ hiện tại. Vì vậy, đó là lý do tại sao tôi muốn thực hiện thay đổi để giữ hành vi hiện tại để thực hiện những điều đúng đắn (làm mới dữ liệu, giao diện người dùng) trong viewWillAppearbộ điều khiển chế độ xem của tôi. Nếu bạn có bất kỳ ý tưởng nào về việc giải quyết vấn đề đó, vui lòng giúp đỡ.
jacob

Tôi tin rằng giải pháp mà tôi liên kết đến ở cuối câu trả lời của mình sẽ giải quyết được điều đó. Phải mất một số công việc để định cấu hình tại thời điểm trình bày, nhưng về cơ bản nó đảm bảo rằng mọi người trình bày (bao gồm cả người trình bày cảnh báo) sẽ nghe thấy khi bộ điều khiển chế độ xem được trình bày bị loại bỏ.
matt

-5

Nó sẽ không đơn giản để gọi PresentViewController.viewWillAppear? cho phép sa thải?

self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)

4
Nó không phải của bạn để gọi.
matt
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.