Need help understanding some swift MacOS crashes - swift

I am trying to understand why the Preference panes for HSTracker (https://github.com/HearthSim/HSTracker/tree/2.1.10/HSTracker) are reporting crashes on the viewDidLoad method.
HSTracker
HSTracker.GeneralPreferences.viewDidLoad() -> () GeneralPreferences.swift:0
HSTracker
#objc HSTracker.GeneralPreferences.viewDidLoad() -> () <compiler-generated>:0
AppKit
-[NSViewController _sendViewDidLoad]
HSTracker
function signature specialization <Arg[0] = Owned To Guaranteed> of Preferences.PreferencesWindowController.init(preferencePanes: [Preferences.PreferencePane], style: Preferences.Preferences.Style, animated: Swift.Bool, hidesToolbarForSingleItem: Swift.Bool) -> Preferences.PreferencesWindowController PreferencesWindowController.swift:37
HSTracker
Preferences.PreferencesWindowController.init(preferencePanes: [Preferences.PreferencePane], style: Preferences.Preferences.Style, animated: Swift.Bool, hidesToolbarForSingleItem: Swift.Bool) -> Preferences.PreferencesWindowController <compiler-generated>:0
HSTracker
HSTracker.AppDelegate.preferences.getter : Preferences.PreferencesWindowController <compiler-generated>:0
HSTracker
#objc HSTracker.AppDelegate.openPreferences(Swift.AnyObject) -> () AppDelegate.swift:583
AppKit
-[NSApplication(NSResponder) sendAction:to:from:]
HSTracker
main AppDelegate.swift:21
0x0 + 0
Using atos, I decoded the crash location to the very first line on the method:
https://github.com/HearthSim/HSTracker/blob/2.1.10/HSTracker/UIs/Preferences/GeneralPreferences.swift#L32
notifyGameStart.state = Settings.notifyGameStart ? .on : .off
So why would it crash on that line, assuming all the IBOutlets are properly linked in the XIB file?
There is similarly strange crash when presenting a property sheet on this code:
newDeck = NewDeck(windowNibName: "NewDeck")
if let newDeck = newDeck {
newDeck.setDelegate(self)
newDeck.defaultClass = currentClass ?? nil
self.window!.beginSheet(newDeck.window!, completionHandler: nil)
}
The crash happens on the newDeck.window! forced unwrap.
I can use some insights on why these two crashes happen and how to avoid/fix them.

Related

Unable to find NIB in bundle only after adding an object to CoreData?

This has been stumping me for a few hours and after looking through all kinds of related questions, I can't seem to figure out an answer.
I am getting a strange NSInternalInconsistencyException error. The error states that it cannot load an NIB with the name CountTableViewController. However, when I first run the app (in both simulator and physical device), I can segue to that view controller just fine. It loads and looks just as it does in the Main.storyboard file. Then, when I navigate back and activate another view controller that loads some test data into the Core Data Stack that I am using, things go very wrong. Using breakpoints and console logs, I can see that the single object has been successfully added into the Core Data Stack. But, when I click on the CountTableViewController again, the app crashes with the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Could not load NIB in bundle: 'NSBundle <FILEPATH> (loaded)' with name
'CountTableViewController''
I have read through every related question I could find, here's a quick list of things I have already tried that were unsuccessful:
Check spelling/case of related files and any place they are referenced
(CountTableViewController.swift, CountTableViewCell.swift, CountReuseCell [reuse id])
Delete references to related files, re-drag into Xcode
Delete references to Main.storyboard and LaunchScreen.storyboard, re-drag into Xcode
Verify that every file is listed correctly under 'Compile Sources' in 'Build Phases' of Project
Verify that storyboards are listed correctly under 'Copy Bundle Resources' in 'Build Phases' of Project
Rewrite as a totally new view controller from scratch (still got same error)
I'm pretty sure it's CoreData related since that seems to be the only difference between when the view controller does and does not work, but I'm pretty new to Swift and iOS dev, so I could be way off target.
I have the stack trace that I will post below. I will try to post some concise parts of the code that I think will be helpful. Thank you in advance for your help!
StackTrace:
0 CoreFoundation 0x000000010721912b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x0000000102a78f41 objc_exception_throw + 48
2 CoreFoundation 0x000000010728e245 +[NSException raise:format:] + 197
3 UIKit 0x00000001043fd098 -[UINib instantiateWithOwner:options:] + 501
4 UIKit 0x00000001040b2687 -[UITableView _dequeueReusableViewOfType:withIdentifier:] + 590
5 UIKit 0x0000000120c4f79d -[UITableViewAccessibility dequeueReusableCellWithIdentifier:] + 147
6 UIKit 0x00000001040b2b6b -[UITableView _dequeueReusableCellWithIdentifier:forIndexPath:usingPresentationValues:] + 148
7 UIKit 0x00000001040b2aa3 -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:] + 89
8 UIKit 0x0000000120c4f8e0 -[UITableViewAccessibility dequeueReusableCellWithIdentifier:forIndexPath:] + 285
9 BSA Inventory Control 0x00000001018d35d6 _T021BSA_Inventory_Control24CountTableViewControllerC05tableF0So07UITableF4CellCSo0iF0C_10Foundation9IndexPathV12cellForRowAttF + 774
10 BSA Inventory Control 0x00000001018d3c4c _T021BSA_Inventory_Control24CountTableViewControllerC05tableF0So07UITableF4CellCSo0iF0C_10Foundation9IndexPathV12cellForRowAttFTo + 92
11 UIKit 0x00000001040ce484 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 778
12 UIKit 0x00000001040cea2a -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 74
13 UIKit 0x00000001040941f6 -[UITableView _updateVisibleCellsNow:isRecursive:] + 3031
14 UIKit 0x00000001040b62e6 -[UITableView layoutSubviews] + 176
15 UIKit 0x000000010403ea6d -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1439
CountTableViewController.swift
import UIKit
import CoreData
class CountTableViewController: UITableViewController {
let coreDataStack = CoreDataStack.shared
var moc: NSManagedObjectContext! = nil
var counts: [Count] = []
override func viewDidLoad() {
super.viewDidLoad()
moc = coreDataStack.viewContext
tableView.register(CountTableViewCell.self, forCellReuseIdentifier: "CountReuseCell")
let nib = UINib(nibName: "CountTableViewController", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "CountReuseCell")
}
func reload() {
counts = Count.items(for: moc, matching: nil, sortedBy: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reload()
tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CountReuseCell", for: indexPath) as? CountTableViewCell else {
fatalError("The dequeued cell is not an instance of CountTableViewCell")
}
let count = counts[indexPath.row]
cell.nameLabel.text = count.name
return cell
}
}
The CountTableViewCell.swift file is just a default Cocoa Touch Class with a subclass of UITableViewCell and a single outlet linking to the nameLabel in the storyboard.
A few quick afterthoughts... please let me know if there's any info you need that I didn't include here. I'm using a Core Data Stack Class and an extension adapted by a professor in an iOS dev class I took a year ago that provides the easy to use .items() functionality... I can also post that if you think that's causing any issues. This is so strange because I have another Core Data entity (Product instead of Count) using almost the exact same code that works perfectly. I can't seem to figure out what is different about this scenario...
Xcode Version 9.2 (9C40b), iOS Version 11.2 (15C107)
Just to wrap things up and hopefully help someone in the future, the issue seems to have been in these lines within the CountTableViewController.swift:
tableView.register(CountTableViewCell.self, forCellReuseIdentifier: "CountReuseCell")
let nib = UINib(nibName: "CountTableViewController", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "CountReuseCell")
As usual, it seems this was a mixture of reusing old code and copy/pasting. It's strange that this code seems to work fine in almost an identical scenario with the Product entity in the same project. Nevertheless, I removed the last two lines above, leaving me with only:
tableView.register(CountTableViewCell.self, forCellReuseIdentifier: "CountReuseCell")
Sure enough, the NSInternalInconsistencyException error was gone!
Last night, I was able to rewrite the CountTableViewController entirely programatically and it is working correctly, so I plan to use that for now. However, it is good to know what was causing this error for the future.
Thanks to #pbasdf for the comment/answer!

How to invoke a func passing it as argument between controllers

I'm trying to create a dynamic menu where I send the text and the action of a button from one controller to the next controller and then I want to generate the button that runs the action, but I'm having trouble with the syntax.
So as an example in the first controller i have:
let vc = storyboard.instantiateViewController(withIdentifier:
"CompetitiveKPIChart") as! CompetitiveKPIChartViewController
vc.Menu = [["Menu1":ClickBack()],["Menu2":ClickBack()],["Menu3":ClickBack()]]
func ClickBack() {
self.dismiss(animated: true, completion: {});
}
and in the second controller:
var Menu : [Dictionary<String,()>] = []
override func viewDidLoad() {
let gesture2 = UITapGestureRecognizer(target: self,action: Menu["Menu1"])
btnBack.addGestureRecognizer(gesture2)
}
How can I call the ClickBack() from the first controller in the second controller GestureRecognizer?
The syntax of your Menu declaration is wrong - it should be () -> () for a void function, not ().
This works in Playground, your syntax does not...
var menu : [Dictionary<String,() -> ()>] = [] // Don't use capitals for variables...
func aFunc() {
print("Hello aFunc")
}
menu = [["aFunc": aFunc]]
menu.first!["aFunc"]!() // Prints "Hello aFunc"
However, I think there is a second problem with your code. Your line
UITapGestureRecognizer(target: self,action: Menu["Menu1"])
cannot compile, as Menu is an array, not a dictionary. I think you probably meant
var menu : Dictionary<String,() -> ()> = [:]
in which case your set-up code should say
vc.menu = ["Menu1":ClickBack, "Menu2":ClickBack, "Menu3":ClickBack]
However, there is a final, and probably insurmountable problem, which is that your code
override func viewDidLoad() {
let gesture2 = UITapGestureRecognizer(target: self, action: menu["Menu1"])
btnBack.addGestureRecognizer(gesture2)
}
will still give an error, since action: needs to be a Selector, which is an #objc message identifier, not a Swift function.
I'm afraid I can't see a good way to make this (rather clever) idea work. Time to refactor?

Address Book crash on iOS10

Selecting a contact from contact picker crashes the app in iOS10.0. Contacts picker is shown using ABPeoplePickerNavigationController like this:
let contactsPicker = ABPeoplePickerNavigationController()
contactsPicker.peoplePickerDelegate = self
self.presentViewController(contactsPicker, animated: true, completion: nil)
Here is the stack trace from crash log:
*** Terminating app due to uncaught exception 'CNPropertyNotFetchedException', reason: 'A property was not requested when contact was fetched.'
*** First throw call stack:
(
0 CoreFoundation 0x0000000105a1c34b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00000001052cd21e objc_exception_throw + 48
2 CoreFoundation 0x0000000105a85265 +[NSException raise:format:] + 197
3 Contacts 0x000000010dc6d96f -[CNContact sectionForSortingByFamilyName] + 160
4 Contacts 0x000000010dc3e18e __55-[CNContact(iOSABCompatibility) overwritePerson:error:]_block_invoke + 44
5 CoreFoundation 0x00000001059ad2fd __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 77
6 CoreFoundation 0x00000001059ad1df -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 207
7 Contacts 0x000000010dc3e0f4 -[CNContact(iOSABCompatibility) overwritePerson:error:] + 240
8 Contacts 0x000000010dc3dfc0 -[CNContact(iOSABCompatibility) detachedPersonWithError:] + 46
9 AddressBookUI 0x00000001057bdd77 -[ABPeoplePickerNavigationController contactPicker:didSelectContact:] + 145
10 ContactsUI 0x0000000112396eb2 -[CNContactPickerViewController pickerDidSelectContact:property:] + 306
11 ContactsUI 0x000000011243ee6f -[CNContactPickerHostViewController pickerDidSelectContact:property:] + 95
12 ContactsUI 0x000000011243f5ec __71-[CNContactPickerExtensionHostContext pickerDidSelectContact:property:]_block_invoke + 66
I have already added NSContactsUsageDescription in the info.plist as discussed on Contact Address book crash on iOS 10 beta but that didn't help and I can't use CNContactPickerViewController as I need to support iOS8 devices.
Imran Raheem
From Erdekhayser's solution (Contact Address book crash on iOS 10 beta)
you can use this method to check CNContactPickerViewController is available?
if (NSClassFromString(#"CNContactPickerViewController")) {
// iOS 9, 10, use CNContactPickerViewController
CNContactPickerViewController *picker = [[CNContactPickerViewController alloc] init];
picker.delegate = self;
picker.displayedPropertyKeys = #[CNContactPhoneNumbersKey];
[pr presentViewController:picker animated:YES completion:nil];
}else{
// iOS 8 Below, use ABPeoplePickerNavigationController
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[pr presentViewController:picker animated:YES completion:nil];
}
The Address Book API was deprecated in iOS 9 in favor of the more object-oriented Contacts Framework.
Instead of using the ABPeoplePickerViewController, move to CNContactPickerViewController.
I was getting the same error, when I was trying to get an emailAddresses from CNContact of delegate method.
Initially, I initialize the contactpicker:
//MARK: Contact Action
#IBAction func getContactListAction(_ sender: Any) {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self
contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKey]
vcObject.present(contactPicker, animated: true, completion: nil)
}
Delegate method:
//MAKE: Contact Delegate
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
picker.dismiss(animated: true, completion: nil)
let name = CNContactFormatter.string(from: contact, style: .fullName)
print(name!)
self.textfieldName.text = name!
for number in contact.phoneNumbers {
print("number ----\(number)")
let mobile = number.value.value(forKey: "digits") as? String
if (mobile?.count)! > 7 {
// your code goes here
print("mobile---\(String(describing: mobile))")
self.textfieldMobileNumber.text = mobile!
}
}
// this line couse the crash ---> print(contact.emailAddresses[0].value(forKey: "value") as! String)
}
I was accessing the email address without declaring in initialization.
Error -- Terminating app due to uncaught exception 'CNPropertyNotFetchedException', reason: 'A property was not requested when contact was fetched.'
CORRECT CODE FOR ACCESSING EMAIL ---
Xcode 10 . and 4.2
//MARK: Contact Action
#IBAction func getContactListAction(_ sender: Any) {
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self
contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKey,CNContactEmailAddressesKey] .
// <--- Here make declaration for accessing the required property from CNContact.
vcObject.present(contactPicker, animated: true, completion: nil)
}
//MAKE: Contact Delegate
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
picker.dismiss(animated: true, completion: nil)
let name = CNContactFormatter.string(from: contact, style: .fullName)
print(name!)
self.textfieldName.text = name!
for number in contact.phoneNumbers {
print("number ----\(number)")
let mobile = number.value.value(forKey: "digits") as? String
if (mobile?.count)! > 7 {
// your code goes here
print("mobile---\(String(describing: mobile))")
self.textfieldMobileNumber.text = mobile!
}
}
// print(contact.emailAddresses[0].value.value(forKey: "labelValuePair") as! String)
for email in contact.emailAddresses {
print("number ----\(email)")
let eml = email.value(forKey: "value") as? String
print("eml --\(eml!)")
}
}

Opening a new window on a different storyboard

I am developing an OS X app with storyboards. I get the Preview.storyboard with an Entry Point on an anonymous window with a custom ViewController. In the AppDelegate class, I get the following function.
func newPreviewWindow(sender: AnyObject) {
let storyboard = NSStoryboard.init(name: "Preview", bundle: nil)
let initialController = storyboard.instantiateInitialController()
initialController!.showWindow(nil)
initialController!.makeKeyAndOrderFront(nil)
}
When running the code, the window shows, but I get the following exception:
2016-08-11 10:27:12.434 MyApp[1090:290439] -[NSWindowController makeKeyAndOrderFront:]: unrecognized selector sent to instance 0x60000008c350
2016-08-11 10:27:12.434 MyApp[1090:290439] -[NSWindowController makeKeyAndOrderFront:]: unrecognized selector sent to instance 0x60000008c350
2016-08-11 10:27:12.440 MyApp[1090:290439] (
0 CoreFoundation 0x00007fff926284f2 __exceptionPreprocess + 178
1 libobjc.A.dylib 0x00007fff97c0ef7e objc_exception_throw + 48
2 CoreFoundation 0x00007fff926921ad -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x00007fff92598571 ___forwarding___ + 1009
4 CoreFoundation 0x00007fff925980f8 _CF_forwarding_prep_0 + 120
5 MyApp 0x0000000100005617
...
Based on the exception message and search on Google and StackOverflow, I tried sending a Selector to the makeKeyAndOrderFront function this way :
func newPreviewWindow(sender: AnyObject) {
let storyboard = NSStoryboard.init(name: "Preview", bundle: nil)
let initialController = storyboard.instantiateInitialController()
let selector = #selector(AppDelegate.newPreviewWindow(_:))
initialController!.showWindow(nil)
initialController!.makeKeyAndOrderFront(selector) // [A]
}
But then I get the following compile error on line [A]: Cannot call value of non-function type '((AnyObject?) -> Void)!'
How is the proper way to open the new window or to pass the Selector?
Thanks! :)
initialController!.makeKeyAndOrderFront(nil) is causing a problem because makeKeyAndOrderFront: is not an NSWindowController method - it belongs to NSWindow (hence the unrecognized selector error). Cast your initial controller to your NSWindowController subclass, then bring the window to the front via the controller's window property:
var windowController: NSWindowController!
#IBAction func showOtherWindow(sender: AnyObject) {
windowController = storyboard.instantiateInitialController() as! NSWindowController
windowController.window?.makeKeyAndOrderFront(nil)
}

WKWebView Help Support

I am able to implement the new WebKit in 7.1 Deployment. I can use it without error on the devices running in iOS8 up. However, when the device falls below iOS8, my WKWebView becomes nil even after the initialization, my suspect was even if you silence webkit and successfully add it on your project and the deployment was 7.1, if the OS actually fall below iOS8 this WebKit becomes unvalable.
I want to confirm this error so I can proceed. Since this webkit was introduced as of the release of swift and iOS8. Thanks
Here is a simple example, where I create a new protocol and extend both UIWebView and WKWebView from the same protocol. With this, it makes a easy to keep track of both these views inside my view controller and both of these use common method to load from url, it makes easy for abstraction.
protocol MyWebView{
func loadRequestFromUrl(url: NSURL!)
}
extension UIWebView:MyWebView{
func loadRequestFromUrl(url: NSURL!){
let urlRequest = NSURLRequest(URL: url)
loadRequest(urlRequest)
}
}
extension WKWebView:MyWebView{
func loadRequestFromUrl(url: NSURL!){
let urlRequest = NSURLRequest(URL: url)
loadRequest(urlRequest)
}
}
// This is a simple closure, which takes the compared system version, the comparison test success block and failure block
let SYSTEM_VERSION_GREATER_THAN_OR_EQUAL: (String, () -> (), () -> ()) -> Void = {
(var passedVersion: String, onTestPass: () -> (), onTestFail: () -> ()) in
let device = UIDevice.currentDevice()
let version = device.systemVersion
let comparisonOptions = version.compare(passedVersion, options: NSStringCompareOptions.NumericSearch, range: Range(start: version.startIndex, end: version.endIndex), locale: nil)
if comparisonOptions == NSComparisonResult.OrderedAscending || comparisonOptions == NSComparisonResult.OrderedSame{
onTestPass()
}else{
onTestFail()
}
}
class ViewController: UIViewController{
var webView: MyWebView!
override func viewDidLoad() {
super.viewDidLoad()
SYSTEM_VERSION_GREATER_THAN_OR_EQUAL("8.0",
{
let theWebView = WKWebView(frame: self.view.bounds)
self.view.addSubview(theWebView)
self.webView = theWebView
},
{
let theWebView = UIWebView(frame: self.view.bounds)
self.view.addSubview(theWebView)
self.webView = theWebView
})
webView.loadRequestFromUrl(NSURL(string: "http://google.com"))
}
}

Categories