我正在尝试创建一个自定义 UITabBar,但是当我呈现 tabBar 的视图控制器之一时会导致内存泄漏
我正在尝试创建一个自定义 tabBarController,但似乎由于呈现不同的视图控制器而导致内存泄漏。当我在不同选项之间切换时,我可以看到内存使用量攀升。我还检查了视图层次结构,发现有一堆 UITransitionView。我的 CustomTabBar
如下:
import UIKit
struct CustomTabBarViewControllerObject {
let title: String
let icon: UIImage
let viewController: UIViewController
let index: Int
}
class CustomTabBar: UIView {
// MARK: - Singleton
/// if you plan on using the CustomTabBar singleton then the following should be set:
/// - viewControllerObjects
/// - presentingVC
/// - selectedIconTintColor
/// - unselectedIconTintColor
static let shared = CustomTabBar(frame: .zero)
//MARK: - Variables
private var _viewControllerObjects: [CustomTabBarViewControllerObject]?
public var viewControllerObjects: [CustomTabBarViewControllerObject]? {
get {
return self._viewControllerObjects
} set {
_viewControllerObjects = newValue
if frame.width > 0 {
updateViewControllers()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
if frame.width > 0 {
updateViewControllers()
}
}
private var viewControllerButtons: [UIButton] = []
private var displayTitles = false
private let iconSize = CGSize(width: 30, height: 30)
public var selectedIconTintColor: UIColor!
public var unselectedIconTintColor: UIColor!
public var presentingVC: UIViewController!
private var selectedIndex = 0
//MARK: - init
private init(_ presentingVC: UIViewController? = nil, frame: CGRect, displayTitles: Bool = false, selectedIconTintColor: UIColor = .text1, unselectedIconTintColor: UIColor = .text3, bgColor: UIColor = .background1) {
self.displayTitles = displayTitles
self.selectedIconTintColor = selectedIconTintColor
self.unselectedIconTintColor = unselectedIconTintColor
self.presentingVC = presentingVC
super.init(frame: frame)
backgroundColor = bgColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Methods
private func updateViewControllers() {
guard let viewControllerObjects = viewControllerObjects else {
return
}
// remove all current icons
for view in subviews {
view.removeFromSuperview()
}
let vcCount = CGFloat(viewControllerObjects.count)
let numberOfSpaces = vcCount+1
let interIconSpacing = (frame.width-(vcCount*iconSize.width))/numberOfSpaces
//let interIconSpacing = (frame.width-(iconSize.width*(CGFloat(vcCount)+1)))/(CGFloat(vcCount)+1)
// app fails if there are too many items in view controller
if interIconSpacing < 5 {fatalError("Too many elements in viewControllerObjects")}
var lastXPosition: CGFloat = 0
let yPosition: CGFloat = frame.height/2-iconSize.height/2
var iconWidthSizeAdjustment: CGFloat = -30
// iterate through viewControllerObjects to add them to the view with icon and action
for vcObject in viewControllerObjects {
iconWidthSizeAdjustment+=30
let vcButton = UIButton(frame: CGRect(origin: CGPoint(x: lastXPosition + interIconSpacing + iconWidthSizeAdjustment, y: yPosition), size: iconSize))
vcButton.setBackgroundImage(vcObject.icon, for: .normal)
vcButton.layoutIfNeeded()
vcButton.subviews.first?.contentMode = .scaleAspectFit
vcButton.addAction(UIAction(title: "", handler: { [unowned self] _ in
if vcObject.index != selectedIndex {
vcObject.viewController.modalPresentationStyle = .fullScreen
self.presentingVC.present(vcObject.viewController, animated: false, completion: nil)
self.presentingVC = vcObject.viewController
self.updateSelected(newSelectedIndex: vcObject.index)
}
}), for: .touchUpInside)
if vcObject.index == selectedIndex {
vcButton.tintColor = selectedIconTintColor
} else {
vcButton.tintColor = unselectedIconTintColor
}
addSubview(vcButton)
viewControllerButtons.append(vcButton)
lastXPosition = lastXPosition + interIconSpacing
}
roundCorners([.topLeft, .topRight], radius: 15)
addShadow(shadowColor: UIColor.text1.cgColor, shadowOffset: CGSize(width: 0, height: -1), shadowOpacity: 0.2, shadowRadius: 4)
}
func updateSelected(newSelectedIndex: Int) {
if viewControllerButtons.indices.contains(selectedIndex) && viewControllerButtons.indices.contains(newSelectedIndex) {
viewControllerButtons[selectedIndex].tintColor = unselectedIconTintColor
viewControllerButtons[newSelectedIndex].tintColor = selectedIconTintColor
selectedIndex = newSelectedIndex
} else {
fatalError("Index does not exist: \(newSelectedIndex)")
}
}
}
这是所有 tabBar 项目继承自的 CustomTabBarViewController 类:
import UIKit
class CustomTabBarViewController: UIViewController {
public let customTabBar = CustomTabBar.shared
public let mainView = UIView(frame: .zero)
public var tabBarHeight: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
setUpTabBar()
}
func setUpTabBar() {
// setting up properties of customTabBar
customTabBar.selectedIconTintColor = .text1
customTabBar.unselectedIconTintColor = .text2
customTabBar.presentingVC = self
customTabBar.backgroundColor = .background1
// adding viewControllers for tabBar
customTabBar.viewControllerObjects = [
CustomTabBarViewControllerObject(title: "Home", icon: Images.home, viewController: UINavigationController(rootViewController: HomeViewController()), index: 0),
CustomTabBarViewControllerObject(title: "Search", icon: Images.search, viewController: UINavigationController(rootViewController: SearchViewController()), index: 1)
]
//adding mainView to view
view.addSubview(mainView)
mainView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mainView.topAnchor.constraint(equalTo: view.topAnchor),
mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
])
// add customTabBar to view
view.addSubview(customTabBar)
customTabBar = false
NSLayoutConstraint.activate([
customTabBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
customTabBar.leftAnchor.constraint(equalTo: view.leftAnchor),
customTabBar.rightAnchor.constraint(equalTo: view.rightAnchor),
customTabBar.heightAnchor.constraint(equalToConstant: tabBarHeight),
])
}
}
I am trying to create a custom tabBarController, but it seems like there is a memory leak caused by presenting the different view controllers. I can see the memory usage climb when I toggle between different options. I also checked the view hierarchy and noticed that there were a bunch of UITransitionViews. My CustomTabBar
is below:
import UIKit
struct CustomTabBarViewControllerObject {
let title: String
let icon: UIImage
let viewController: UIViewController
let index: Int
}
class CustomTabBar: UIView {
// MARK: - Singleton
/// if you plan on using the CustomTabBar singleton then the following should be set:
/// - viewControllerObjects
/// - presentingVC
/// - selectedIconTintColor
/// - unselectedIconTintColor
static let shared = CustomTabBar(frame: .zero)
//MARK: - Variables
private var _viewControllerObjects: [CustomTabBarViewControllerObject]?
public var viewControllerObjects: [CustomTabBarViewControllerObject]? {
get {
return self._viewControllerObjects
} set {
_viewControllerObjects = newValue
if frame.width > 0 {
updateViewControllers()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
if frame.width > 0 {
updateViewControllers()
}
}
private var viewControllerButtons: [UIButton] = []
private var displayTitles = false
private let iconSize = CGSize(width: 30, height: 30)
public var selectedIconTintColor: UIColor!
public var unselectedIconTintColor: UIColor!
public var presentingVC: UIViewController!
private var selectedIndex = 0
//MARK: - init
private init(_ presentingVC: UIViewController? = nil, frame: CGRect, displayTitles: Bool = false, selectedIconTintColor: UIColor = .text1, unselectedIconTintColor: UIColor = .text3, bgColor: UIColor = .background1) {
self.displayTitles = displayTitles
self.selectedIconTintColor = selectedIconTintColor
self.unselectedIconTintColor = unselectedIconTintColor
self.presentingVC = presentingVC
super.init(frame: frame)
backgroundColor = bgColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Methods
private func updateViewControllers() {
guard let viewControllerObjects = viewControllerObjects else {
return
}
// remove all current icons
for view in subviews {
view.removeFromSuperview()
}
let vcCount = CGFloat(viewControllerObjects.count)
let numberOfSpaces = vcCount+1
let interIconSpacing = (frame.width-(vcCount*iconSize.width))/numberOfSpaces
//let interIconSpacing = (frame.width-(iconSize.width*(CGFloat(vcCount)+1)))/(CGFloat(vcCount)+1)
// app fails if there are too many items in view controller
if interIconSpacing < 5 {fatalError("Too many elements in viewControllerObjects")}
var lastXPosition: CGFloat = 0
let yPosition: CGFloat = frame.height/2-iconSize.height/2
var iconWidthSizeAdjustment: CGFloat = -30
// iterate through viewControllerObjects to add them to the view with icon and action
for vcObject in viewControllerObjects {
iconWidthSizeAdjustment+=30
let vcButton = UIButton(frame: CGRect(origin: CGPoint(x: lastXPosition + interIconSpacing + iconWidthSizeAdjustment, y: yPosition), size: iconSize))
vcButton.setBackgroundImage(vcObject.icon, for: .normal)
vcButton.layoutIfNeeded()
vcButton.subviews.first?.contentMode = .scaleAspectFit
vcButton.addAction(UIAction(title: "", handler: { [unowned self] _ in
if vcObject.index != selectedIndex {
vcObject.viewController.modalPresentationStyle = .fullScreen
self.presentingVC.present(vcObject.viewController, animated: false, completion: nil)
self.presentingVC = vcObject.viewController
self.updateSelected(newSelectedIndex: vcObject.index)
}
}), for: .touchUpInside)
if vcObject.index == selectedIndex {
vcButton.tintColor = selectedIconTintColor
} else {
vcButton.tintColor = unselectedIconTintColor
}
addSubview(vcButton)
viewControllerButtons.append(vcButton)
lastXPosition = lastXPosition + interIconSpacing
}
roundCorners([.topLeft, .topRight], radius: 15)
addShadow(shadowColor: UIColor.text1.cgColor, shadowOffset: CGSize(width: 0, height: -1), shadowOpacity: 0.2, shadowRadius: 4)
}
func updateSelected(newSelectedIndex: Int) {
if viewControllerButtons.indices.contains(selectedIndex) && viewControllerButtons.indices.contains(newSelectedIndex) {
viewControllerButtons[selectedIndex].tintColor = unselectedIconTintColor
viewControllerButtons[newSelectedIndex].tintColor = selectedIconTintColor
selectedIndex = newSelectedIndex
} else {
fatalError("Index does not exist: \(newSelectedIndex)")
}
}
}
This is the CustomTabBarViewController class that all tabBar items inherit from:
import UIKit
class CustomTabBarViewController: UIViewController {
public let customTabBar = CustomTabBar.shared
public let mainView = UIView(frame: .zero)
public var tabBarHeight: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
setUpTabBar()
}
func setUpTabBar() {
// setting up properties of customTabBar
customTabBar.selectedIconTintColor = .text1
customTabBar.unselectedIconTintColor = .text2
customTabBar.presentingVC = self
customTabBar.backgroundColor = .background1
// adding viewControllers for tabBar
customTabBar.viewControllerObjects = [
CustomTabBarViewControllerObject(title: "Home", icon: Images.home, viewController: UINavigationController(rootViewController: HomeViewController()), index: 0),
CustomTabBarViewControllerObject(title: "Search", icon: Images.search, viewController: UINavigationController(rootViewController: SearchViewController()), index: 1)
]
//adding mainView to view
view.addSubview(mainView)
mainView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mainView.topAnchor.constraint(equalTo: view.topAnchor),
mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
])
// add customTabBar to view
view.addSubview(customTabBar)
customTabBar = false
NSLayoutConstraint.activate([
customTabBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
customTabBar.leftAnchor.constraint(equalTo: view.leftAnchor),
customTabBar.rightAnchor.constraint(equalTo: view.rightAnchor),
customTabBar.heightAnchor.constraint(equalToConstant: tabBarHeight),
])
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
所以我在这里冒险,但
我认为这就是问题所在。这个函数被这个系统相当快地调用,
将其与这一行结合起来
,基本上你就有了一个灾难的秘诀。这只会不断添加阴影。可能还有其他对象被重新添加,但这是我在没有完整测试和调试的情况下注意到的。基本上阴影相当昂贵。他们使用大量的计算和内存。这基本上每次都会重新添加阴影。我首先评论添加阴影,看看这是否会减少资源使用。如果没有,则注释掉layoutSubviews()中的updateViewController()。
So I am going out on a limb here but in
I think this is where the problem lies. This function gets called by this system fairly rapidly
Combine that with this line
And basically you have a recipe for disaster. This just keeps adding shadows. There may be other objects getting re added as well but this is what I noticed without full testing and debug. Basically shadows are fairly expensive. They use both a decent amount of computing as well as ram. This will basically re add the shadow every time. I would start by commenting the add shadow and seeing if that reduces resource usage. If it doesn't then comment out the updateViewController() in layoutSubviews().
您可以使用仪器进行调试,以查看导致峰值的分配情况。
https://www.raywenderlich.com/16126261-instruments-快速入门教程
You can debug using instruments to see what is being allocated causing the spike.
https://www.raywenderlich.com/16126261-instruments-tutorial-with-swift-getting-started