Blank/Black view when attempting to set delegate/data source - xcode

I am attempting to load a view from a .xib file as per this question:
Assign xib to the UIView in Swift
My setup is as follows:
StripView.xib — the .xib file from which I am loading the view, contains a single UIView
StripView.swift — the 'owner' of the UIView in StripView.xib, contains references/IBOutlets for each component in StripView.xib
StripViewController.swift — The view controller, which loads an instance of StripView and handles most of the logic/data source/delegate stuff.
This all worked fine in Xcode 6 Beta 4, however, upgrading to Xcode 6 Beta 5 (the latest) breaks. On app launch, I simply get a blank, black screen. All other parts of the app seem to work fine. This bug only occurs when I attempt to set the delegate or data source of a UIPickerView (owned by StripView) to the StripViewController.
Code snippet:
class StripViewController: SWRevealViewController, RSColorPickerViewDelegate, UIPickerViewDataSource, UIPickerViewDelegate, BLEDelegate, UIActionSheetDelegate, UITextFieldDelegate {
var stripView: StripView!
var colorPicker: RSColorPickerView!
var connectBtn:UIButton!
var connectionSpinner:UIActivityIndicatorView!
var tapRecognizer:UITapGestureRecognizer!
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// use UIView extension as per https://stackoverflow.com/questions/24370061/assign-xib-to-the-uiview-in-swift
stripView = UIView.loadFromNibNamed("StripView") as StripView
self.view.addSubview(stripView)
// nav bar setup //
self.title = "Strip \(self.tabBarController.selectedIndex+1)"
let leftNav:UIView = UIView(frame: CGRectMake(0,0,100,40))
self.connectBtn = UIButton(frame: CGRectMake(0,0,95,40))
connectBtn.frame = CGRectMake(0,0,connectBtn.bounds.size.width,connectBtn.bounds.size.height)
connectBtn.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
connectBtn.setTitle("Connect", forState: UIControlState.Normal)
connectBtn.setTitleColor(UIColor(red: 0.0, green: 122.0/255.0, blue: 1.0, alpha: 1.0), forState: UIControlState.Normal)
connectBtn.addTarget(self, action: "connectButtonPressed:", forControlEvents: UIControlEvents.TouchDown)
leftNav.addSubview(connectBtn)
self.connectionSpinner = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
connectionSpinner.frame = CGRectMake(connectBtn.frame.minX + connectBtn.bounds.size.width, connectBtn.frame.minY + 10, connectionSpinner.bounds.size.width, connectionSpinner.bounds.size.height)
connectionSpinner.hidesWhenStopped = true
leftNav.addSubview(connectionSpinner)
let leftNavItem:UIBarButtonItem = UIBarButtonItem(customView: leftNav)
self.navigationItem.leftBarButtonItem = leftNavItem
// set up color picker
colorPicker = RSColorPickerView(frame: CGRectMake(20, 78, 160, 160))
self.view.addSubview(colorPicker)
colorPicker.delegate = self
// set up mode picker
// THESE APPEAR TO BE THE OFFENDING LINES —
// COMMENTING THEM OUT DISPLAYS THE VIEW AS EXPECTED
self.stripView.modePicker.dataSource = self;
self.stripView.modePicker.delegate = self;
// set up notifications
NSNotificationCenter.defaultCenter().addObserver(self, selector: "colorFieldDidChange:", name: "ColorFieldDidChangeNotification", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
// set up UI and populate fields
self.stripView.setAnimSpeed()
// set up BLE
BLEManager.instance.ble.delegate = self
}
[...]
Once more, the offending lines appear to be:
self.stripView.modePicker.dataSource = self;
self.stripView.modePicker.delegate = self;
If I comment them out, all else displays as normal. I even attempted moving the delegate/data source into StripView.swift (not StripViewController), but I see the same result.
Here's the part of StripViewController that implements UIPickerViewDataSource and UIPickerViewDelegate methods:
func numberOfComponentsInPickerView(pickerView: UIPickerView!) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView!, numberOfRowsInComponent component: Int) -> Int {
return DataModel.instance.ModeNames.count
}
func pickerView(pickerView: UIPickerView!, titleForRow row: Int, forComponent component: Int) -> String! {
return DataModel.instance.ModeNames[row]
}
func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int) {
// send mode to arduino
println("--- sending mode select command ---")
var buf:[UInt8] = [0x00, 0x00, 0x00, 0x00]
buf[0] = Modes.instance.SET_MODE
buf[1] = UInt8(DataModel.instance.ModeOpcodes[DataModel.instance.ModeNames[row]]!)
let data:NSData = NSData(bytes: buf, length: 4)
BLEManager.instance.ble.write(data)
}
Did something change in the way the Swift language handles delegates/data sources, or view loading from a XIB?
EDIT:
I've narrowed down the problem to the DataModel singleton I'm using as a datasource. That code (DataModel.swift) looks like so, implementing a basic singleton pattern as per https://github.com/hpique/SwiftSingleton :
class DataModel {
// setup singleton
class var instance: DataModel {
struct Static {
static let instance : DataModel = DataModel()
}
return Static.instance
}
// modes
// eventually may replace this with a more flexible data structure
// for now, we just need names
let ModeNames: Array<String> = ["Light All", "Rainbow"]
let ModeOpcodes: Dictionary<String, UInt8> = [DataModel.instance.ModeNames[0]: Modes.instance.LIGHT_ALL, DataModel.instance.ModeNames[1]: Modes.instance.RAINBOW];
}
Replacing instances of this with a variable defined in StripViewController results in things working fine. Something about it being a singleton seems to be throwing things off. Thoughts? Perhaps the singleton is not being instantiated correctly?

One thing easily overlooked is connecting the data source and delegate to the view controller itself. In addition to ctrl dragging an outlet, you need to ctrl drag the data source and delegate from the connections inspector to the small view controller icon on top of the view in the storyboard.

Related

UITextView in collectionview cell on mac spikes cpu

I have a simple UIKit application that has a UITextView in a UICollectionViewCell. The app is designed for iOS/iPadOS and works just fine on those platforms. However, when run on Mac (Designed for iPad) as soon as I start scrolling the collectionview, the cpu usage spikes to ~85% and stays there indefinitely. The only way to lower the cpu is to click outside of the application window, but once it comes to the foreground again, the cpu usage jumps right back up. I've tried running on Mac in Catalyst mode too, but the same problem occurs with slightly less cpu usage (~45%).
Additionally the debugger constantly spits out [API] cannot add handler to 3 from 3 - dropping while scrolling.
Does anyone have an explanation or solutions for this?
I’m using Xcode Version 14.1 (14B47b) on macOS Ventura 13.0 (22A380).
class ViewController: UIViewController {
var dataSource: UICollectionViewDiffableDataSource<Section, String>! = nil
var collectionView: UICollectionView! = nil
var items = Array(0...100).map{"Item \($0)"}
enum Section: String {
case main
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
configureCollectionView()
configureDataSource()
applyInitialSnapshot()
}
private func createLayout() -> UICollectionViewLayout {
return UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])
return NSCollectionLayoutSection(group: group)
}
}
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .systemBackground
view.addSubview(collectionView)
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<TestCell, String> { (cell, indexPath, item) in
cell.configure(title: item, row: indexPath.item)
}
dataSource = UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) {
(collectionView, indexPath, identifier) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
}
private func applyInitialSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
class TestCell: UICollectionViewCell {
private let annotationsTextView = UITextView()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(title: String, row: Int) {
annotationsTextView.attributedText = .init(string: "Row: \(row) Item: \(title)", attributes: [.font: UIFont.preferredFont(forTextStyle: .title1)])
}
private func addViews() {
annotationsTextView.isScrollEnabled = false
annotationsTextView.isEditable = false
annotationsTextView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(annotationsTextView)
NSLayoutConstraint.activate([
annotationsTextView.topAnchor.constraint(equalTo: contentView.topAnchor),
annotationsTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
annotationsTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
annotationsTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
}
I am still on 13.0.1 and had the same problem (tons of [API] cannot add handler to 3 from 3 - dropping). I narrowed it down to a call to UITableView.scrollToNearestSelectedRow with animated = true.
Setting animated to false stops the logging. I guess I’ll have to check out 13.1
I'm experiencing this exact behavior in Ventura whenever there is a UITextView anywhere in the view hierarchy with either isSelectable or isEditable set to true, but only while the UITextView is NOT first responder. This occurs whether the UITextView is visible or hidden.
The excess CPU usage can be prevented by any of the following:
Remove the UITextView from the view hierarchy.
Set isSelectable AND isEditable to false.
Make the UITextView first responder.
I'm still investigating and will update here if I find more. We should all probably report the issue to Apple as I imagine they will need to fix this at the system level.
2022-12-01: Update
This issue appears to have been resolved as of macOS 13.1 beta 4 (22C5059b). Hallelujah!

NSTableView - Better solution for sorting collection with NSSortDescriptor

I have a NSTableView with 2 columns bound with a custom type (SelectedFiles) array as File Name and File Path, after clicking the header, I want it to sort the data in ascending / descending order, I tried these codes with NSSortDescriptor:
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
tableView.tableColumns[0].sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
let selectedFilesArray = NSMutableArray(array: selectedFiles)
selectedFilesArray.sort(using: tableView.sortDescriptors) // Signal SIGABRT
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
My custom collection for the data in table view:
struct SelectedFiles: CustomStringConvertible {
let fileName: String
let filePath: String
var description: String {
return "\(fileName) at path \(filePath)"
}
}
var selectedFiles: [SelectedFiles] = []
It turns out it doesn't work at all, IDK if its anything wrong with my code or I'm missing something.
So, I came up with this awkward solution:
var tableViewSortingOrder = ComparisonResult.orderedAscending
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
switch tableViewSortingOrder {
case .orderedAscending:
tableViewSortingOrder = .orderedDescending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
default:
tableViewSortingOrder = .orderedAscending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
tableView.reloadData()
}
}
After I changed to this solution, it worked perfectly as it switches swiftly between ascending / descending order. But, when it comes to deleting objects in the collection, it throws Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when I'm trying to delete multiple objects from both collection and table view with some specific files.
So, I'm thinking if I should change a way of achieving this header sorting thing by using NSSortDescriptor (use the old-fashioned way by correcting my first method) in order to get away from this issue, I have to admit that my second way is a bit of awkward (is more like a plan C).
I've red through multiple StackOverflow posts on this topic and I tried all of their ways, especially this one, I am not using CoreData which its solutions does not work for my situation.
Anyone can help point out the way please? 😊
I red the guide to NSTableView from Apple Developer Site and few other StackOverflow posts, I found myself a workable solution for Swift 4:
I set the sortDescriptorPrototype to fileNameSortDescriptor in viewDidLoad() under ViewController class.
class ViewController: NSViewController {
override func viewDidLoad()
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare))
let tableColumn = tableView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileNameColumn"))!
tableColumn.sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
And then I added an inheritance from NSObject and inserted #objcMembers to prevent warning: Object <#object#> of class '<#class#>' does not implement methodSignatureForSelector: -- trouble ahead from occurring and then cause Signal SIGABRT while calling selectedFiles.sort(using: tableView.sortDescriptors) (Reference: Object X of class Y does not implement methodSignatureForSelector in Swift).
#objcMembers class SelectedFiles: NSObject {
let fileName: String
let filePath: String
override var description: String {
return "\(fileName) at path \(filePath)"
init(fileName: String, filePath: String) {
self.fileName = fileName
self.filePath = filePath
}
}
Here's the code for tableView(_:sortDescriptorsDidChange:) in NSTableViewDataSource:
extension ViewController: NSTableViewDataSource {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
var selectedFilesArray = NSArray(array: selectedFiles)
selectedFilesArray = selectedFilesArray.sortedArray(using: tableView.sortDescriptors) as NSArray
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
Now, everything works perfectly fine.

Inconsistent View Hierarchy

I try to add a .xib based custom view into another .xib based custom view.
The result is looking like this:
for sub in v.subviews {
Swift.print(v.subviews) // returns array [sub]
Swift.print(sub) // returns sub
Swift.print(sub.superview) // return nil!
}
How can a view be in superview's subviews, but the superview property not correctly set? Can this happen during de/coding? What do I need to set in order for this to be correct?
The next question would be, why sub is shown correctly in "View Debugging" but not in when I need it during run time.
EDIT: (thanks Matt for looking into this)
My Code looks like this:
import AppKit
func showXIBDefinedInPanel(name: String, title: String ) {
if let w = loadXIBDefined(name: name) {
let c = NSViewController()
c.view = w
let window = NSPanel(contentViewController: c)
window.isMovable = true
window.collectionBehavior = .canJoinAllSpaces
window.tabbingMode = .disallowed
window.title = title
window.styleMask = [ .titled, .utilityWindow, .closable]
window.makeKeyAndOrderFront(w)
}
}
func loadXIBDefined(name: String) -> XIBDefined? {
var topLevelObjects : NSArray?
var result : XIBDefined? = nil
if Bundle.main.loadNibNamed(NSNib.Name(rawValue: name), owner: nil, topLevelObjects: &topLevelObjects) {
result = topLevelObjects!.first(where: { $0 is XIBDefined }) as? XIBDefined
}
return result
}
///used to embed a XIBDefined into another XIB
#IBDesignable class XIBEmbedder : NSView {
// Our custom view from the XIB file
var view: NSView!
var xibName: String!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
view = loadXIBDefined(name: xibName)
addSubview(view)
self.frame = view.frame
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
view = loadXIBDefined(name: xibName)
addSubview(view)
self.frame = view.frame
}
init(name: String) {
self.xibName = name
super.init(frame: NSZeroRect)
view = loadXIBDefined(name: xibName)
addSubview(view)
self.frame = view.frame
}
}
///used as class for XIB based Custom Views
#IBDesignable class XIBDefined: NSView {
///I had an issue with an oddly moved frame, so I hard coded a fix
override func setFrameOrigin(_ newOrigin: NSPoint) {
super.setFrameOrigin(NSZeroPoint)
needsDisplay = true
}
}
#IBDesignable class WelcomeCMapView : XIBEmbedder {
init() {
super.init(name: "Welcome Concept Maps")
}
required init?(coder decoder: NSCoder) {
super.init(name: "Welcome Concept Maps")
}
}
Key are the two classes XIBDefined and XIBEmbedder. Former one can be loaded from a XIB, the latter can be used in an XIB. Therefore the later embedded XIB uses XIBDefined as the custom class, the embedding XIB has a WelcomeCMapView as custom class.
The problem-showing code on top is part of the post-processing, which is executed within viewDidLoad() from the NSViewController loading the embedding XIB.

Implementing NSSegmentedControl in NSToolbar to control NSTabViewController

In my macOS application, I'm trying to replicate the Photos.app implementation of NSSegmentedControl in NSToolbar to control an NSTabViewController. For reference, here's what that looks like:
So, my approach was as follows:
Hide the default NSTabView header using the Interface Builder
Programmatically add an NSToolbar
Insert NSSegmentedControl as an NSToolbarItem.
Use a #selector to listen for changes to NSSegmentedControl.
Here's the current implementation:
class WindowController: NSWindowController, NSToolbarDelegate {
// MARK: - Identifiers
let mainToolbarIdentifier = NSToolbar.Identifier("MAIN_TOOLBAR")
let segmentedControlIdentifier = NSToolbarItem.Identifier("MAIN_TABBAR")
// MARK: - Properties
var tabBar: NSSegmentedControl? = NSSegmentedControl(labels: ["One", "Two"], trackingMode: NSSegmentedControl.SwitchTracking.selectOne, target: self, action: #selector(didSwitchTabs))
var toolbar: NSToolbar?
var tabBarController: NSTabViewController?
// MARK: - Life Cycle
override func windowDidLoad() {
super.windowDidLoad()
self.toolbar = NSToolbar(identifier: mainToolbarIdentifier)
self.toolbar?.allowsUserCustomization = false
self.toolbar?.delegate = self
self.tabBar?.setSelected(true, forSegment: 0)
self.tabBarController = self.window?.contentViewController as? NSTabViewController
self.tabBarController?.selectedTabViewItemIndex = 0
self.window?.toolbar = self.toolbar
}
// MARK: - NSToolbarDelegate
public func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
var toolbarItem: NSToolbarItem
switch itemIdentifier {
case segmentedControlIdentifier:
toolbarItem = NSToolbarItem(itemIdentifier: segmentedControlIdentifier)
toolbarItem.view = self.tabBar
case NSToolbarItem.Identifier.flexibleSpace:
toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
default:
fatalError()
}
return toolbarItem
}
public func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [segmentedControlIdentifier, NSToolbarItem.Identifier.flexibleSpace]
}
public func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier.flexibleSpace, segmentedControlIdentifier, NSToolbarItem.Identifier.flexibleSpace]
}
// MARK: - Selectors
#objc func didSwitchTabs(sender: Any) {
let segmentedControl = sender as! NSSegmentedControl
if (segmentedControl.selectedSegment == 0) {
self.tabBarController?.selectedTabViewItemIndex = 0
} else if (segmentedControl.selectedSegment == 1) {
self.tabBarController?.selectedTabViewItemIndex = 1
}
}
}
And, here it is in action:
Now, I am new to macOS development and this feels like it's a very complicated and convoluted way of solving this problem. Is there an easier way I could achieve the same thing ? Perhaps somehow in Interface Builder ? What could be done to improve here ? What have I done wrong ?
Thanks for your time.
For anybody implementing NSSegmentedControl on the toolbar and it did not trigger IBAction, I got the same problem and pay my half-day to resolve this.
The problem is I connect my segmented with the NSWindowController class.
To fix this, create a subclass of NSWindow, set that class to base class of window on your storyboard, then create #IBOutlet #IBAction link to NSWindow. Remember, link it with NSWindowController will not work.

Computed property causes application crash

I'm porting the Apple class ImageAndTextCell to swift and use it inside a NSTableView, it was a trivial task but when I click on table row the application crashes.
I suppose the crash is due to a deallocation problem but I don't understand how to fix it.
The app crashes with the error: Thread 1: EXC_BAD_ADDRESS (code=EXC_i386_GPFLT), no other message is present so debugging the error is hard
I've isolated the code to easily reproduce the crash.
My ImageAndTextCell is shown below, it declares an icon property and access to it using the computed property image
import Foundation
import Cocoa
class ImageAndTextCell : NSTextFieldCell {
private var icon : NSImage?
override var image : NSImage! {
get {
return icon
}
set {
if newValue !== icon {
icon = newValue
if let im = newValue {
im.size = NSMakeSize(CGFloat(16.0), CGFloat(16.0))
}
}
}
}
deinit { println(" is being deinitialized") }
override func copyWithZone(zone: NSZone) -> AnyObject! {
var cell = super.copyWithZone(zone) as ImageAndTextCell
cell.icon = icon
return cell
}
}
The Application delegate is
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var window: NSWindow!
var items = [String]()
override func awakeFromNib() {
items.append("/Users/dave/trash/test.txt")
}
func numberOfRowsInTableView(tableView: NSTableView!) -> Int {
return self.items.count
}
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject! {
return self.items[row]
}
func tableView(tableView: NSTableView, willDisplayCell cell:AnyObject!, forTableColumn tableColumn:NSTableColumn!, row:NSInteger) {
let item = self.items[row]
var fieldCell = cell as NSTextFieldCell
fieldCell.drawsBackground = false
// commenting the line below the app works fine but obviously the cell doesn't show the image
fieldCell.image = NSWorkspace.sharedWorkspace().iconForFile(item)
}
}
copyWithZone and deinit are called every time I click on the row so I suppose some pointer is released before the time
The table cell 'custom class' is set inside Interface Builder to ImageAndTextCell
Any idea?

Resources