Swift:以模态方式呈现并解除导航控制器

2024-01-20

我有一个非常常见的 iOS 应用场景:

The MainVC该应用程序的一个UITabBar控制器。我在 AppDelegate.swift 文件中将此 VC 设置为 rootViewController:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    window = UIWindow()
    window?.rootViewController = MainVC()
    window?.makeKeyAndVisible()
}

当用户注销时,我会显示一个导航控制器登陆创投作为导航堆栈的根视图控制器。

let navController = UINavigationController(rootViewController: LandingVC)
self.present(navController, animated: true, completion: nil)

Inside 登陆创投您单击“登录”按钮,然后LoginVC被压入栈顶。

navigationController?.pushViewController(LoginVC(), animated: true)

当用户成功登录时,我从 LoginVC 内部解雇()导航控制器。

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

基本上,我试图实现以下流程:

一切正常,但问题是LoginVC从来没有从内存中释放。因此,如果用户登录并注销 4 次(没有理由这样做,但仍然有机会),我会看到LoginVC内存中4次,LandingVC 0次。

我不明白为什么LoginVC没有被释放,但是登陆创投 is.

在我看来(并纠正我错误的地方),因为导航控制器已呈现并且它包含 2 个 VC(登陆创投 and LoginVC),当我在里面使用dismiss()时LoginVC它应该关闭导航控制器,因此两者都包含 VC。

  • MainVC: 介绍VC
  • 导航控制器: 提交VC

来自苹果文档:

呈现视图控制器负责解除它呈现的视图控制器。如果您在呈现的视图控制器本身上调用此方法,UIKit 会要求呈现的视图控制器处理解雇。

我相信当我关闭导航控制器时出现了问题LoginVC。有没有办法在里面触发dismiss()MainVC(出示 VC)用户一登录?

PS:使用下面的代码不会解决这个问题,因为它会弹出到导航堆栈的根视图控制器,即 LandingVC;而不是MainVC。

self.navigationController?.popToRootViewController(animated: true)

任何帮助将非常感激!

=====================================

我的LoginVC代码:

import UIKit
import Firebase
import NotificationBannerSwift

class LoginVC: UIViewController {

    // reference LoginView
    var loginView: LoginView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // dismiss keyboard when clicking outside textfields
        self.hideKeyboard()

        // setup view elements
        setupView()
        setupNavigationBar()
    }

    fileprivate func setupView() {
        let mainView = LoginView(frame: self.view.frame)
        self.loginView = mainView
        self.view.addSubview(loginView)

        // link button actions from LoginView to functionality inside LoginViewController
        self.loginView.loginAction = loginButtonClicked
        self.loginView.forgotPasswordAction = forgotPasswordButtonClicked
        self.loginView.textInputChangedAction = textInputChanged

        // pin view
        loginView.translatesAutoresizingMaskIntoConstraints = false
        loginView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        loginView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        loginView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        loginView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}

    fileprivate func setupNavigationBar() {
        // make navigation controller transparent
        self.navigationController?.navigationBar.isTranslucent = true
        self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
        self.navigationController?.navigationBar.shadowImage = UIImage()

        // change color of text
        self.navigationController?.navigationBar.tintColor = UIColor.white

        // add title
        navigationItem.title = "Login"

        // change title font attributes
        let textAttributes = [
            NSAttributedStringKey.foregroundColor: UIColor.white,
            NSAttributedStringKey.font: UIFont.FontBook.AvertaRegular.of(size: 22)]
        self.navigationController?.navigationBar.titleTextAttributes = textAttributes
    }


    fileprivate func loginButtonClicked() {
        // some local authentication checks

        // ready to login user if credentials match the one in database
        Auth.auth().signIn(withEmail: emailValue, password: passwordValue) { (data, error) in
            // check for errors
            if let error = error {
                // display appropriate error and stop rest code execution
                self.handleFirebaseError(error, language: .English)
                return
            }


            // if no errors during sign in show MainTabBarController
            guard let mainTabBarController = UIApplication.shared.keyWindow?.rootViewController as? MainTabBarController else { return }

            mainTabBarController.setupViewControllers()

            // this is where i dismiss navigation controller and the MainVC is displayed
            self.navigationController?.dismiss(animated: true, completion: nil)
        }
    }

    fileprivate func forgotPasswordButtonClicked() {
        let forgotPasswordViewController = ForgotPasswordViewController()

        // present as modal
        self.present(forgotPasswordViewController, animated: true, completion: nil)
    }

    // tracks whether form is completed or not
    // disable registration button if textfields not filled
    fileprivate func textInputChanged() {
        // check if any of the form fields is empty
        let isFormEmpty = loginView.emailTextField.text?.count ?? 0 == 0 ||
        loginView.passwordTextField.text?.count ?? 0 == 0

        if isFormEmpty {
            loginView.loginButton.isEnabled = false
            loginView.loginButton.backgroundColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 0.6)
        } else {
            loginView.loginButton.isEnabled = true
            loginView.loginButton.backgroundColor = UIColor(red: 32/255, green: 215/255, blue: 136/255, alpha: 1.0)
        }
    }
}

经过大量搜索后,我想我找到了解决方案:

启发我的是所有评论这个问题的人以及这篇文章:

https://medium.com/@stremsdoerfer/understanding-memory-leaks-in-closures-48207214cba https://medium.com/@stremsdoerfer/understanding-memory-leaks-in-closures-48207214cba

我将从我的编码哲学开始:我喜欢保持代码分离和干净。因此,我总是尝试创建一个包含我想要的所有元素的 UIView,然后将其“链接”到适当的视图控制器。但是当 UIView 有按钮并且按钮需要执行操作时会发生什么?众所周知,视图内部没有“逻辑”的空间:

class LoginView: UIView {

    // connect to view controller
    var loginAction: (() -> Void)?
    var forgotPasswordAction: (() -> Void)?

    // some code that initializes the view, creates the UI elements and constrains them as well

    // let's see the button that will login the user if credentials are correct
    let loginButton: UIButton = {
        let button = UIButton(title: "Login", font: UIFont.FontBook.AvertaSemibold.of(size: 20), textColor: .white, cornerRadius: 5)
        button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
        button.backgroundColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 0.6)
        return button
    }()

    // button actions
    @objc func handleLogin() {
        loginAction?()
    }

    @objc func handleForgotPassword() {
        forgotPasswordAction?()
    }
}

所以正如文章所说:

LoginVC有很强的参考意义登录查看,这对登录动作 and 忘记密码操作刚刚创建了对 self 的强引用的闭包。

正如你所看到的,我们有一个循环。这意味着,如果退出此视图控制器,它无法从内存中删除,因为它仍然被闭包引用。

这可能就是我的 LoginVC 从未从内存中释放的原因。 [剧透警告:这就是原因!]

如问题所示,LoginVC 负责执行所有按钮操作。我是什么之前做的 was:

class LoginVC: UIViewController {

    // reference LoginView
    var loginView: LoginView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
    }

    fileprivate func setupView() {
        let mainView = LoginView(frame: self.view.frame)
        self.loginView = mainView
        self.view.addSubview(loginView)

        // link button actions from LoginView to functionality inside LoginVC

        // THIS IS WHAT IS CAUSING THE RETAIN CYCLE <--------------------
        self.loginView.loginAction = loginButtonClicked
        self.loginView.forgotPasswordAction = forgotPasswordButtonClicked

        // pin view
        .....
    }

    // our methods for executing the actions
    fileprivate func loginButtonClicked() { ... }
    fileprivate func forgotPasswordButtonClicked() { ... }

}

现在我知道是什么导致了保留周期,我需要找到一种方法并打破它。正如文章所说:

要打破一个循环,你只需要打破一个引用,并且你会想要打破最简单的一个。在处理闭包时,您总是希望断开最后一个链接,即闭包引用的内容。

为此,您需要在捕获变量时指定您不需要强链接。您有两个选择:弱或无主,并且您在关闭一开始就声明它。

所以我改变了什么LoginVC实现这一目标的方法是:

fileprivate func setupView() {

    ...
    ...
    ...

    self.loginView.loginAction = { [unowned self] in
        self.loginButtonClicked()
    }

    self.loginView.forgotPasswordAction = { [unowned self] in
        self.forgotPasswordButtonClicked()
    }

    self.loginView.textInputChangedAction = { [unowned self] in
        self.textInputChanged()
    }
}

在这个简单的代码更改之后(是的,我花了 10 天才弄清楚),一切都像以前一样运行,但记忆正在感谢我。

有几点要说:

  1. 当我第一次注意到这个内存问题时,我责怪自己没有正确地关闭/弹出视图控制器。您可以在我之前的 StackOverflow 问题中找到更多信息:ViewController、内存消耗和代码效率 https://stackoverflow.com/questions/50860140/viewcontrollers-memory-consumption-and-code-efficiency?noredirect=1#comment89004875_50860140

  2. 在这个过程中,我学到了很多关于呈现/推送视图控制器和导航控制器的知识;因此,尽管我的方向是错误的,但我确实学到了很多东西。

  3. 没有什么是免费的,内存泄漏教会了我这一点!

希望我可以帮助其他人和我有同样问题的人!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Swift:以模态方式呈现并解除导航控制器 的相关文章

  • 当 isUserInteractionEnabled false 时,SKSpriteNode 不会让触摸通过

    我正在尝试在 SpriteKit 中创建一个覆盖层 方法是使用SKSpriteNode 但是 我希望触摸穿过覆盖层 所以我设置isUserInteractionEnabled为假 然而 当我这样做时 SKSpriteNode似乎仍然吸收所有
  • Apple Mach-O 链接器错误(静态,不是 ld)

    我最近遇到了 Apple Mach O 链接器错误 大多数指南建议将 构建设置 中的位码更改为 否 但它仅适用于 ld 错误 这与我的不同 我会提供截图 请帮忙修复bug pod HandySwift 导致了错误的出现 这是它的 Githu
  • 将 UIToolBar 添加到所有键盘(swift)

    我正在尝试以尽可能少的重复次数将自定义 UIToolBar 添加到我的所有键盘中 我目前的做法要求我将代码添加到所有 viewDidLoads 中 并将每个文本字段的委托分配给我正在使用的 viewController 我尝试创建自己的 U
  • 如何使用 CNContacts 快速获取手机号码?

    我有一些代码可以检索用户联系人中的所有电话号码 但只想过滤掉手机号码 目前 我只是通过将第一个数字为 或第二个数字为 7 的数字添加到数组中来实现此目的 如下所示 func findContacts gt CNContact let key
  • 如何为 iPhone 6+、6 和 5 指定不同尺寸?

    我想让 iPhone 6 6 和 5 上的视图看起来几乎相同 在附图中 我的意思是 例如 取消 按钮在 iPhone 5 中距离屏幕左边缘应为 30 像素 在 6 中为 35 像素 在 6 中为 45 像素 其他元素也类似 如何为每种类型设
  • Swift:设置协议的可选属性

    如何设置协议的可选属性 例如 UITextInputTraits 有许多可选的读 写属性 当我尝试以下操作时 出现编译错误 无法分配给 textInputTraits 中的 keyboardType func initializeTextI
  • iPhone X 将对象底部与安全区域对齐会破坏其他设备上的外观

    关于 iPhone X 自动布局怪癖的问题 我有两个按钮 以前这些按钮将与超级视图底部对齐 偏移量为 20 以免它们接触屏幕底部 此后我将链接更改为安全区域而不是超级视图 Here s the original setup Looks go
  • 有没有办法在 Firebase 中等待查询完成?

    我正在使用 TableView 在 Viewcontroller 中的 iOS 应用程序中进行查询 我想确保在继续加载 TableView 之前我的查询已经返回 有没有办法保证查询已经完成 None
  • 使用数组中的字符串淡入/淡出标签

    func setOverlayTitle self overlayLogo text Welcome var hello String Bon Jour GUTEN nMORGEN BONJOUR HOLA BUENOS D AS BUON
  • 关闭捕获上下文 Swift

    当我尝试更改闭包中的变量时出现此错误 A C function pointer cannot be formed from a closure that captures context 是否有解决方法或者仍然可以更改闭包内的变量 My C
  • 使用 UITabBarController 时覆盖整个屏幕的视图?

    我想在 UITabBarController 设置中在整个屏幕上覆盖 HUD 样式的透明图形 执行此操作的按钮位于第一个选项卡的屏幕 FirstViewController 中 并且覆盖层也应该覆盖选项卡 这可能吗 您可以将新视图直接附加到
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • 防止 iOS 键盘在 cordova 3.5 中滚动页面

    我正在使用 Cordova 3 5 和 jQuery mobile 构建 iOS 应用程序 我在大部分应用程序中禁用了滚动功能 但是 当我选择输入字段时 iOS 键盘会打开并向上滚动页面 我不想要这个功能 由于输入足够高 键盘不会覆盖它 我
  • Xcode 异步单元测试在主线程上等待

    我正在尝试使用 Xcode 中的单元测试来测试一些异步代码 但主线程被阻塞 问题在于 某些正在测试的代码期望从 iOS 类 AVFoundation 接收回调 但是 AVFoundation 类似乎只会在主线程上回调 问题是 如果我正在进行
  • 诊断和仪器均缺少“僵尸”选项

    运行 Xcode 4 0 2 Zombie 选项丢失 其他 SO 帖子建议找到它的两个地方 Product gt Run looks like this Product gt Profile looks like this 奇怪的是 我之前
  • 在 Object 子类及其自己的子类上实现ignoreProperties()

    我是领域新手 我正在使用继承自 Object 的基类以及该基类的自定义子类创建模型 我的模型要求基类通过覆盖静态来声明一些属性被忽略ignoredProperties 方法 当尝试在某些基类子类上重写该方法时 我收到一个 Swift 编译器
  • 使用强光混合模式时突出显示伪影

    我正在 iPhone 应用程序中使用顶部图像的 HardLight 混合模式混合两个图像 它看起来像这样 UIGraphicsBeginImageContext size sourceImage drawInRect rectangle b
  • 上传存档错误:“缺少 iOS 发行版签名身份......”

    我正在尝试使用 Xcode 将我的 iOS 应用程序存档上传到 iTunes Connect 但是当我单击 上传到 App Store 时 出现错误 Xcode 尝试查找或生成匹配的签名资产并 由于以下问题未能做到这一点 缺少 iOS 为
  • 致命错误:在 Swift 中解包可选值时意外发现 nil

    所以我试图获取 Swift 中输入字段的文本 这就是我得到的 class ViewController UIViewController IBOutlet var passwordField UITextField IBOutlet var
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两

随机推荐