I have been using the Xcode memory graph debugger to find cyclic references in our project and I've found a few of them.
However, I haven't been able to see the cycles in the graph. Only by inspecting the code.
For instance I'll see ...
ViewControllerA ---[parentViewController]---> ViewControllerB
But in code they are created like ...
class ViewControllerA: UIViewController {
let parentViewController: UIViewController
}
and...
class ViewControllerB: UIViewController {
let otherViewController: UIViewController!
viewDidLoad() {
...
otherViewController = ViewControllerA(parentViewController: self)
}
}
Clearly this is a cyclic reference. But it only shows one arrow in the graph.
Is there a way to make this show both arrows in the graph?
Just created an example...
New Project - Single view - Edit ViewController.swift to...
import UIKit
class ViewController: UIViewController {
var other: ViewControllerB!
override func viewDidLoad() {
super.viewDidLoad()
other = ViewControllerB(other: self)
}
}
class ViewControllerB: UIViewController {
let other: UIViewController
init(other: UIViewController) {
self.other = other
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In Memory Graph Debugger...
Focus on ViewController...
Focus on ViewControllerB...
From these I can infer that there is a reference cycle. But there are tutorials on the web where it actually shows the cycle with arrows following a cycle around the objects...
Like this from Use your loaf
Reference cycles only appear in the memory graph debugger for leaked objects. Because ViewController is referenced by UIWindow, it's not considered leaked, so the cycle between the view controllers isn't displayed.
Related
I constantly find "leaks" using Xcode instruments (8.2) where free is the last call on the object/memory. If free is being called successfully then why would instruments be reporting this as a leak? Is this really a leak or a bug in instruments?
Here is one example
For example, this small segment of code seems to cause instruments to report leaks like these.
class RDTranslateComponentView: RDMessageCellComponentView {
let textView: RDTextMessageCellComponentView
let button: UIButton
convenience init() {
self.init(frame: CGRect.zero)
}
override init(frame: CGRect) {
self.textView = RDTextMessageCellComponentView()
self.button = UIButton(type: .custom)
super.init(frame: frame)
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
self.textView.label.font = UIFont.proximaSemiBold(withSize: 14)
self.textView.label.textColor = UIColor.remindBrand()
self.textView.bubble.backgroundColor = UIColor.white
self.addSubviews([ self.textView, self.button ])
self.button.addTarget(self, action: #selector(pressedButton(_:)), for: .touchUpInside)
}
#objc private func pressedButton(_ sender: UIButton?) {
/// Code that calls into a delegate
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return self.textView.sizeThatFits(size)
}
override func layoutSubviews() {
super.layoutSubviews()
self.textView.frame = self.bounds
self.button.frame = self.bounds
}
}
class RDMessageCellComponentView: UIView {
/// The inside of this is just a bag of constants. Doesn't even touch anything View related so I'm omitting.
}
Here's a stack trace from my code in association to a leak. Keep in mind this isn't the only place this happens. This case props up often though and seems to be exclusive to code that has UILabel instantiated inside the init method of a subview that is added as a subview to another view.
I have a UIView class where I create some buttons and labels programmatically, and I have a view inside a certain ViewController that is attached to this class. The class works perfectly fine as expected, but whenever I try to link this custom UIView class to a xib view i get these errors:
-"Main.storyboard: error: IB Designables: Failed to update auto layout status: Interface Builder Cocoa Touch Tool crashed"
-"Rendering the view took longer than 200 ms. Your drawing code may suffer from slow performance.
"
-"error: IB Designables: Failed to render instance of MyView: Rendering the view took longer than 200 ms. Your drawing code may suffer from slow performance.
"
Here is the initialization code in the UIView class:
#IBDesignable class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func loadFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
func setup() {
var view = loadFromNib()
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
}
Both the class and the xib file have the same name,
UPDATE: I found out that the:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
Is the one responsible for all the errors, but if I deleted it's content and ran the app the view doesn't show as expected, I also tried awakeFromNib() and same issues happened.
Not quite sure what is causing your specific errors, but here is what helped me when I was having trouble connecting a UIView in a xib file to a custom UIView class:
1) Delete the contents of your DerivedData sub-folder:
(The contents of DerivedData will be recreated when you build your projects again.)
Your folder can be found by visiting the "Locations" tab in your XCode preferences.
Just delete the folders inside and re-build / run your project again.
2) Clean your project
Product -> Clean (Or just use the [command + shift + K] command.
3) Restart xcode and re-build / run your code.
Hope this helps!
I'm using a TableView and have a custom TableViewCell that I've added a subview to.
The problem is that I need the subview's height to change sometimes and therefore, the table's contentView would have to be updated as well as the row's height.
The subview of the custom TableViewCell is represented by the yellow background.
These images show what's currently happening in my simulator.
On Load
After the event that causes the subview's height to increase
What's the best approach to take with something like this?
Should I use constraints? And if so, what kind of constraints should I use? Would I have to then reload the tableview too every time the subview's size changes?
Here is the code I'm currently using for my custom TableViewCell:
import UIKit
class CustomCell: UITableViewCell {
var newView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.newView = UIView(frame: self.frame)
self.newView.backgroundColor = .yellowColor()
self.addSubview(newView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.newView.frame.size.width = self.frame.size.width // because self.frame.width is different than it was in the init method
}
func somethingHappenedThatMySubviewHasToIncreaseInHeight() {
self.newView.frame.size.height = self.frame.size.height + 40
}
}
The best approach is to use Auto Layout and self-sizing cells. Setup constraints in storyboard for your custom cell.
You will not need to reload the tableView. Each cell will automatically adjust its height, based on how much vertical space its subview takes.
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.
I'm having a heck of a time setting up a simple split view. The first split view is collapsed. I need to set a minimum width for it. Everything I see online (scarce for NSSplitViewController/NSSplitView) is for Objective-C, puts everything in the app delegate, and uses XIBs.
Here's the scenario:
Window Controller with a segue to a SplitView Controller, which has two split views (2 view controllers).
Which object needs to have the NSSplitViewDelegate?
EDIT: Adding code snippet:
For example, I have this:
import Cocoa
class ViewController: NSSplitViewController, NSSplitViewDelegate {
#IBOutlet weak var pdlSplitView: NSSplitView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func splitView(splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
return proposedMinimumPosition + 200
}
}
Is there more that I'm missing?
Thanks
UPDATE
Based on comments below, I've made a change, but now I get a sigAbort on the class definition for the AppDelegate. Full code
ViewController:
import Cocoa
class ViewController: NSSplitViewController, NSSplitViewDelegate {
#IBOutlet weak var pdlSplitView: NSSplitView!
let publicDataListings : PDL = PDL()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.pdlSplitView.delegate = self
}
override func splitView(splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
return proposedMinimumPosition + 200
}
}
SidebarViewController:
import Cocoa
class SidebarViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
DatasetViewController:
import Cocoa
class DatasetViewController: NSViewController, NSSplitViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
Update
I took away my custom NSSplitViewController class and created two NSSplitView classes, one with the constraint method. Now, I see both subviews, but they're far smaller than they should be:
Is there anyone at all that has done this with Swift and Storyboards?
No coding is required to set a minimum width in a storyboard with auto layout for a NSSplitViewController/NSSplitView.
Select the CustomView that you require a minimum width for (e.g. 200), and add a width constraint set to the required value which will add a "Equal" constraint (e.g. Custom View.Width equals 200).
Next locate that new constraint and change the constraint relation to "Greater Than or Equal" (e.g. so you now have width ≥ 200).
You now have a minimum width in an NSSplitView. You can then use the Priority field to resolve any conflicts with any other auto layout constraints.
These values are not exposed in the storyboard, which is a great shame, but NSSplitViewItem has minimumThickness and maximumThickness properties which you can use. (This overrides the holding priority, so if you set minimumThickness for one splitViewItem, the other one(s) will now shrink into nothing if you make the window small enough.)
There is also automaticMaximumThickness (I cannot work out how this interacts with the other values) and preferredThicknessFraction which had no effect when I played with it under 10.13.
Set NSSplitViewController as delegate of NSSplitView (the split view you want to constrain). In your case it should be - in xib hook the delegate outlet of the NSSplitView to file owner (I guess the file owner is NSSplitViewController subclass)
Implement
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex { ... }
in NSSplitViewController
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSSplitViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSSplitViewDelegate/splitView:constrainMinCoordinate:ofSubviewAt:
I have a window with an outlet and a custom view (in my .xib file) which contains a button and a text field. When a button is pressed in the window I want to add an instance of the custom view into the window.
Currently I have an outlet to the window and the custom view (configWindow and customView) and this action is called when the button is pressed:
#IBAction func addView(sender: NSButton) {
configWindow.contentView.addSubview(customView)
// Print the views in the window, see what's been added
for i in configWindow.contentView.subviews {
println(i)
}
}
This will only ever add one view to the window.
Is this the right way to go about it, or should I be using a completely different approach?
You can't add the same view twice. It sounds like you are trying to add the same instance of customView to configWindow multiple times, which you can't do. If you think about it, it's fairly obvious why -- how will the superview manage two subviews which are the same? How will it know the difference between the two of them?
You should be adding different instances of the CustomView class instead:
#IBAction func addView(sender: NSButton) {
let customView = CustomView(frame: <some frame>)
configWindow.contentView.addSubview(customView)
// Print the views in the window, see what's been added
for i in configWindow.contentView.subviews {
println(i)
}
}
Edited to add
I've created an example project that you can download at https://bitbucket.org/abizern/so-27874883/get/master.zip
This basically initialises multiple views out of a nib file and adds them randomly to a view.
The Interesting part is:
class CustomView: NSView {
#IBOutlet weak var label: NSTextField!
class func newCustomView() -> CustomView {
let nibName = "CustomView"
// Instantiate an object of this class from the nib file and return it.
// Variables are explicitly unwrapped, since a failure here is a compile time error.
var topLevelObjects: NSArray?
let nib = NSNib(nibNamed: nibName, bundle: NSBundle.mainBundle())!
nib.instantiateWithOwner(nil, topLevelObjects: &topLevelObjects)
var view: CustomView!
for object: AnyObject in topLevelObjects! {
if let obj = object as? CustomView {
view = obj
break
}
}
return view
}
}
Where I create a factory method of the custom class that loads itself from the nib, and then returns the first top level object of the correct class.