รับ UIViewController ยอดนิยม


192

ฉันไม่สามารถดูเหมือนจะได้รับด้านบนมากที่สุดโดยไม่ต้องเชื่อมต่อกับUIViewController UINavigationControllerนี่คือสิ่งที่ฉันมี:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

อย่างไรก็ตามมันดูเหมือนจะไม่ทำอะไรเลย keyWindowและrootViewControllerดูเหมือนจะเป็นค่าที่ไม่ใช่ศูนย์เกินไปดังนั้นการผูกมัดตัวเลือกที่ไม่ควรจะมีปัญหา

หมายเหตุ: มันเป็นความคิดที่ดีที่จะทำสิ่งนี้ มันแบ่งรูปแบบ MVC


ต่อไปนี้เป็นโซลูชันทางเลือกทางเลือกหนึ่งที่พร้อมใช้งานstackoverflow.com/a/39994115/1872233
iDevAmit

ที่เกี่ยวข้อง: รับ viewController a uiView is on
Honey

คำตอบ:


287

presentViewControllerแสดงตัวควบคุมมุมมอง มันไม่ส่งคืนตัวควบคุมมุมมอง หากคุณไม่ได้ใช้UINavigationControllerคุณอาจกำลังมองหาpresentedViewControllerและคุณจะต้องเริ่มต้นที่รูทและวนซ้ำตามมุมมองที่นำเสนอ

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

สำหรับ Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

สำหรับ iOS 13+

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}

1
มีคนอธิบายห่วงลูปได้ไหม สำหรับฉันดูเหมือนว่าไม่มีอะไรจะวนซ้ำ; ฉันไม่แน่ใจด้วยซ้ำว่าทำไมถึงมีการรวบรวม
ศาสตราจารย์ทอม

15
@ProfessorTom การวนซ้ำจะดำเนินต่อไปตราบใดที่topController.presentedViewControllerส่งคืนบางสิ่ง (เช่นตัวควบคุมมีตัวควบคุมลูกที่นำเสนอ) มันคือwhile letการบังคับใช้ความจริงที่ว่าtopController.presentedViewControllerต้องคืนบางสิ่งบางอย่าง ถ้ามันคืนค่าศูนย์ (นั่นคือตัวควบคุมไม่มีลูกที่นำเสนอ) แล้วมันจะหยุดการวนซ้ำ ในเนื้อความของการวนซ้ำมันกำหนดลูกเป็นกระแสtopControllerและวนซ้ำอีกครั้งโดยลงไปตามลำดับชั้นของตัวควบคุมมุมมอง สามารถกำหนดใหม่ได้topControllerเนื่องจากเป็นคำสั่งvarด้านนอก if
rickerbh

1
ขอบคุณ. ฉันไม่สามารถหาตัวอย่างออนไลน์while letได้ แน่นอนมีif letตัวอย่างมากมายที่จะพบ
ศาสตราจารย์ทอม

1
let x = somethingThatCouldBeNilไวยากรณ์เป็นเคล็ดลับที่มีประโยชน์สุดที่จะใช้ที่ใดก็ได้มูลค่าความจริง / สภาพสามารถนำมาใช้ หากเราไม่ได้ใช้ที่นี่เราจะต้องกำหนดค่าอย่างชัดเจนจากนั้นทดสอบเพื่อดูว่าเป็นจริงหรือไม่ ฉันคิดว่ามันกระชับและแสดงออกจริงๆ
rickerbh

1
ฉันคุ้นเคยกับเคล็ดลับมันเป็นเพียงเล็กน้อยยากที่จะให้เหตุผลในขณะที่ลูป - ซึ่งฉันได้พบตัวอย่างที่ขาดแคลน - โดยเฉพาะอย่างยิ่งนี้
ศาสตราจารย์ทอม

272

มีส่วนขยายนี้

สวิฟท์ 2. *

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

สวิฟท์ 3

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }
        return controller
    }
}

คุณสามารถใช้สิ่งนี้ได้ทุกที่บนคอนโทรลเลอร์ของคุณ

if let topController = UIApplication.topViewController() {

}

1
ขอบคุณสำหรับเคล็ดลับการขยายของคุณ :)
Thein

4
ฉันพยายามทำการแก้ไขที่สำคัญสำหรับคำตอบนี้ แต่มันถูกปฏิเสธ (ฉันไม่รู้ว่าทำไมและเหตุผลของเทมเพลตที่ให้มาไม่สมเหตุสมผล): มันเป็นสิ่งสำคัญที่จะตรวจสอบว่า nav.visibleViewController เป็นศูนย์หรือไม่ก่อนที่จะใช้ในการเรียกซ้ำ โทร (เช่นเดียวกับวิธีการตรวจสอบ tab.selectedViewController) เพราะมิฉะนั้นถ้ามันเป็นศูนย์คุณจะเข้าไปวนซ้ำไม่สิ้นสุด
Ethan G

@EthanG ตามความเข้าใจของฉันถ้า nav.visibleViewController เป็นศูนย์ฟังก์ชันจะส่งกลับศูนย์ (เลื่อนไปที่สุดท้ายreturn) มันจะเข้าสู่วงวนไม่สิ้นสุดได้อย่างไร?
Desmond DAI

3
ฉันคิดว่ามันจะมีเหตุผลมากกว่าที่จะทำให้สิ่งนี้เป็นฟังก์ชั่นคงที่ของ UIViewController
Leszek Zarna

1
การตรวจสอบ 'presentViewController' น่าจะมาก่อนถ้าคุณต้องการจับตัวควบคุมมุมมองที่นำเสนออย่างสุภาพใน UITabBarControllers ..
Tokuriku

65

สำหรับ swift 4/5 + เพื่อรับ viewController สูงสุด

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

วิธีใช้

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}

2
ทางออกที่ยอดเยี่ยม ขอบคุณ!
Andrey M.

2
'keyWindow' เลิกใช้แล้วใน iOS 13.0
rs7

2
'keyWindow' เลิกใช้แล้วใน iOS 13.0 stackoverflow.com/a/57899013/4514671
Rebeloper

19
extension UIWindow {

    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

การใช้งาน:

if let topController = window.visibleViewController() {
    println(topController)
}

วิธีการแก้ปัญหานี้ดูดีมาก แต่ฉันพยายามเรียกใช้ตัวควบคุม view เพื่อดูเมื่อฉันได้รับการแจ้งเตือนแบบพุชและทำให้เกิดข้อผิดพลาดที่ศูนย์เมื่อreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike

@ ไมค์คุณจำเป็นต้องใช้ presentationViewController เท่านั้นไม่นำเสนอ ViewController presentedViewController
อัลแลร์

@allaire หากคุณนำเสนอตัวควบคุมมุมมอง modal ที่ด้านบนของตัวควบคุมมุมมอง modal แล้วคุณต้องการ.presentedViewController.presentedViewControllerหรือไม่?
Baran Emre

6

ขึ้นอยู่กับคำตอบ Dianz รุ่น Objective-C

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}

จะไม่ทำงานกับ UINavigationController ใน UITabBarController มันจะกลับ UINavigationController ควรกลับ topController ในการนำทางติดอยู่
Mike.R

Tnx Tnx Tnx Bro
reza_khalafi

6

ฉันชอบคำตอบของ @ dianzและนี่คือเวอร์ชั่นของ swift 3 โดยพื้นฐานแล้วมันเหมือนกัน แต่เขาไม่มีวงเล็บปีกกาและชื่อไวยากรณ์ / ตัวแปร / วิธีการบางส่วนได้เปลี่ยนไป ดังนั้นนี่คือ!

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

การใช้งานยังคงเหมือนเดิมทุกประการ:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}

6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 ฉันทำการทดสอบบางอย่างเกี่ยวกับคำตอบและความคิดเห็นในเว็บไซต์นี้ สำหรับฉันแล้วงานต่อไปนี้

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

จากนั้นรับ viewController สูงสุดโดย:

UIApplication.shared.topMostViewController()

5

ใช้รหัสนี้เพื่อค้นหา UIViewController ที่ติดอันดับต้น ๆ

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}

2
สิ่งนี้แตกต่างจากคำตอบของ rickerbh อย่างไร
ElectroBuddha

5

การเปลี่ยนแปลงเล็กน้อยบน @AlberZou โดยใช้ตัวแปรที่คำนวณได้มากกว่าฟังก์ชั่น

extension UIViewController {
  var topMostViewController : UIViewController {

    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }

    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }

    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }

    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

จากนั้นพูดว่า

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}

4

จาก Bob -c ด้านบน:

Swift 3.0

extension UIWindow {


    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController  = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {

        if vc.isKind(of: UINavigationController.self) {

            let navigationController = vc as! UINavigationController
            return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)

        } else if vc.isKind(of: UITabBarController.self) {

            let tabBarController = vc as! UITabBarController
            return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)

        } else {

            if let presentedViewController = vc.presentedViewController {

                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)

            } else {

                return vc;
            }
        }
    }
}

4

มีรสชาติมากเกินไป แต่ไม่มีผู้ใดทำซ้ำซ้ำอีก รวมจากรายการก่อนหน้า:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }

2

คุณสามารถกำหนดตัวแปร UIViewController ใน AppDelegate และในทุก viewWillAppear ตั้งค่าตัวแปรเป็น self (อย่างไรก็ตามคำตอบ dianz คือคำตอบที่ดีที่สุด)

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}

1
ขอบคุณมากมันใช้งานได้ดีสำหรับฉันเป็นโซลูชันอื่นเมื่อพยายามรับการนำทางควบคุมส่งคืนศูนย์ดังนั้นฉันจึงไม่สามารถผลักดัน vc ใหม่ได้
Amr Angry

ตรวจสอบให้แน่ใจว่า currentVC ที่กำหนดเป็นข้อมูลอ้างอิงอ่อนหรือคุณจะมีหน่วยความจำรั่ว
bubuxu

2

วิธีค้นหา viewController ที่มองเห็นได้ใน Swift 3

if let viewControllers = window?.rootViewController?.childViewControllers {

     let prefs = UserDefaults.standard

     if viewControllers[viewControllers.count - 1] is ABCController{
        print("[ABCController] is visible")

     }
}

รหัสนี้ค้นหาตัวเพิ่มล่าสุดหรือตัวควบคุมที่ใช้งานล่าสุดที่มองเห็นได้

สิ่งนี้ฉันใช้ใน AppDelegate เพื่อค้นหาตัวควบคุมมุมมองที่ใช้งานอยู่


2
import UIKit

extension UIApplication {

    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }

    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}

การใช้งานที่ชัดเจนของ 'visibleViewController'
Omar N Shamali

1

คุณใส่รหัสไว้ที่ไหน

ฉันลองรหัสของคุณในการสาธิตของฉันฉันพบว่าถ้าคุณใส่รหัสใน

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

จะล้มเหลวเนื่องจากมีการตั้งค่าหน้าต่างคีย์

แต่ฉันใส่รหัสของคุณในตัวควบคุมมุมมองบางอย่าง

override func viewDidLoad() {

มันใช้งานได้


didFinishLaunchingWithOptionsมันไม่ได้อยู่ใน ฉันแค่ต้องการสิ่งนี้เพื่อการแก้ปัญหาต่าง ๆ
Zoyt

1

ในกรณีที่หายากมากด้วยการกำหนดเองตัวควบคุมมุมมองด้านบนส่วนใหญ่ไม่ได้อยู่ในการนำทางสแต็กหรือตัวควบคุมแถบแท็บหรือนำเสนอ

ในสถานการณ์เช่นนี้จำเป็นต้องตรวจสอบUIApplication.shared.keyWindow.subviews.last == self.viewดูว่าตัวควบคุมมุมมองปัจจุบันเป็นอันดับต้น ๆ หรือไม่


1

สำหรับทุกคนที่กำลังมองหาโซลูชันที่รวดเร็ว 5 / iOS 13+ ( keywindowเลิกใช้แล้วตั้งแต่ iOS 13)

extension UIApplication {

    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}

ฉันจะใช้มันอย่างไร
Chris Comas

แค่เรียกมันว่าอย่างนี้ UIApplication.getTopMostViewController()ภายใน ViewController ของคุณ @ChrisComas
Virendra

0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }

0

ทางออกที่ดีที่สุดสำหรับฉันคือส่วนขยายที่มีฟังก์ชั่น สร้างไฟล์ swift ด้วยนามสกุลนี้

ประการแรกคือส่วนขยาย UIWindow :

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

ภายในฟังก์ชั่นการเพิ่มไฟล์

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

และถ้าคุณต้องการใช้คุณสามารถโทรหาได้ทุกที่ ตัวอย่าง :

  override func viewDidLoad() {
    super.viewDidLoad()
      if let topVC = visibleViewController() {
             //show some label or text field 
    }
}

รหัสไฟล์เป็นดังนี้:

import UIKit

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.