I had written a function below which is inside Framework A:
open func invokeTracking(key: String, bundle: Bundle? = Bundle.main) {
let qos = DispatchQoS.QoSClass.default
DispatchQueue.global(qos: qos).async {
self.invokeAsyncTracking(key: String, bundle: bundle)
}
And I am calling this in an Xcode project in multiple places and also from other frameworks.
In Xcode project:
override func viewDidLoad() {
super.viewDidLoad()
HelperClass.invokeTracking(key: "Camino", bundle: Bundle? = Bundle.main)
}
Similarly calling it from Framework B
func callMainTracking() {
super.viewDidLoad()
HelperClass.invokeTracking(key: "Carthage", bundle: Bundle? = Bundle.main)
}
This is working fine and has worked for almost a year. Recently I added another additional default parameter to the invoke tracking method like this so as not to affect the existing method calls like below. And I am trying to set the value of this parameter of a global variable:
open func invokeTracking(key: String, bundle: Bundle? = Bundle.main, conventDict: [String: Any] = [:]) {
mainDict = conventDict
let qos = DispatchQoS.QoSClass.default
DispatchQueue.global(qos: qos).async {
self.invokeAsyncTracking(key: String, bundle: bundle)
}
The app is crashing in the main thread on the line mainDict = conventDict when trying to access that default parameter. If I do it inside the async its crashing in that async thread. But this crash is happening only when it's called from Framework B and not when its called from the Xcode project. Also this crash is happening only on the device.
I have tried the following to resolve this:
Tried to declare default parameter as optional and using if let to unwrap it.
Tried with different data types.
Tried by changing arguments order.
And the only way it does not crash is if I send a value for this default parameter from Framework B while calling this method.
func callMainTracking() {
super.viewDidLoad()
HelperClass.invokeTracking(key: "Carthage", bundle: Bundle? = Bundle.main, conventDict: ["test": "123"])
}
But this defeats the purpose of a usage of a default parameter. Im new to debugging Xcode crashes so any help would be appreciated.
I used the Instruments and tried to find a Zombie but there wasnt one. The app simply just crashed and no zombie process was detected.
Related
I am try to make UITestCase for UIViewcontrollers, But when I load main storyboard in my QuizAppUITests, It could not identify from Bundle and it's gives below error
Could not find a storyboard named 'Main' in bundle NSBundle </Users/mac/Library/Developer/CoreSimulator/Devices/CC199C69-F398-4A7C-882E-BFD3E72B95D3/data/Containers/Bundle/Application/BCC3F88D-E08C-4491-8340-699EAF98AB28/QuizAppUITests-Runner.app> (loaded) (NSInvalidArgumentException)
I have added Main Storyboard in QuizAppUITests Target as well
And below is my code for test
import XCTest
#testable import QuizApp
class QuizViewControllerUITests: XCTestCase {
func makeSUT() -> QuizViewController {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle(for: type(of: self)))
let sut = storyboard.instantiateViewController(withIdentifier: "QuizViewController") as! QuizViewController
_ = sut.view
return sut
}
func test_loadQuizViewController() {
let sut = makeSUT()
sut.headerQuestion = "Q1"
XCTAssertEqual(sut.headerQuestion, "Q1")
}
}
Is there any required to change in BuildSetting of QuizAppUITests Target?
None of this makes sense for a UI test. Instead, put your code into your unit test target, which is QuizAppTests.
My book iOS Unit Testing by Example has a chapter called "Load View Controllers". Looking there for what it says about storyboard-based view controllers:
Don't include the storyboard in your test target. Put it only in your app target.
Then you can load the storyboard with UIStoryboard(name: "Main", bundle: nil)
There's a new way to instantiate the view controller that doesn't require force-casting. Instead of instantiateViewController(withIdentifier:), use instantiateViewController(identifier:) and assign it to an explicitly typed variable.
Like this:
let sut: QuizViewController = storyboard.instantiateViewController(
identifier: "QuizViewController"
)
Since you use the name of the class as the identifier, we can even get rid of the string. This protects us from typos, at compile time:
let sut: QuizViewController = storyboard.instantiateViewController(
identifier: String(describing: QuizViewController.self)
)
Finally, instead of _ = sut.view, we can be more explicit about loading the view:
sut.loadViewIfNeeded()
This hooks up the outlet connections.
Again, all this belongs in your unit test target QuizAppTests, not your UI test target QuizAppUITests.
For your actual tests, don't just assign a property in your system under test and check that it was assigned. That doesn't prove anything. Instead, I'd focus on testing:
That outlets are not nil
That interacting with controls does what you want
That navigation works
That view appearance hasn't changed from an approved snapshot
This can all be done with TDD.
I am building a Cocoa app for production and when I create an NSViewController for routing without NSStoryboard instantiate, I got the error like below.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: ContentFully.AnotherController in bundle (null).
Actually I solved my problem via using an NSViewController to adding NSStoryboard but I would like to learn what is going on when I call it programmatically and why did crash?
Working scenario
let storyboard = NSStoryboard(name: "Dashboard", bundle: nil)
let vc = storyboard.instantiateInitialController() as! AnotherController
Crashed scenario
let vc = AnotherController()
self.view.window?.contentViewController = vc
Even if I create a class fully programmatically and non-relational with NSStoryboard I could not use it when I change the contentViewController. Is it necessary to search every controller in NSBundle for Swift?
Thanks in advance
A very simple working version would be:
import Cocoa
class AnotherController: NSViewController {
override func loadView() {
view = NSView(frame: NSMakeRect(0.0, 0.0, 400.0, 270.0))
let label = NSTextField(labelWithString: "Another Controller")
view.addSubview(label)
}
}
I can call that from my first controller using the code you say crashes and get the expected result.
#IBAction func replaceMe(_ sender: Any) {
let vc = AnotherController()
self.view.window?.contentViewController = vc
}
I'm trying to use QuickLookController subclass as a child controller, setting its view as a subview in the parent. However, it always displays "no file to preview" message in the opening window. URL in the data source is valid, but the controller is never trying to get it! func previewItemAt index is never invoked!
func "numberOfPreviewItems" invokes always.
Please, help!
I get it. driven by example in article https://williamboles.me/hosting-viewcontrollers-in-cells/ I loaded my controller from bundle:
static func createFromStoryBoard() -> PreviewControler {
let storyboard = UIStoryboard(name: "PreviewControler", bundle: Bundle(for: PreviewControler.self))
guard let viewController = storyboard.instantiateViewController(withIdentifier: "PreviewControler") as? PreviewControler else {
fatalError("PreviewControler should be present in storyboard")
}
return viewController
}
But QuickLook controller must be created with it's constructor, so change to
let viewController = PreviewController()
solved the problem. Now all is fine.
So basically I am trying to run the function Refresh (Located in ViewController.swift) in AppDelegate.swift. After searching for the better part of 5 hours I can't figure out why this isnt working.
here is my function:
func Refresh(){
if(Count==0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent])
Current.text=String(TipArray[CountCurrent])
Deliveries.text="\(Runs)"
}
if(Count>0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent-1])
Current.text=String(Total)
Deliveries.text="\(Runs)"
}
}
In my Appdelegate.swift I have initialized the ViewController by setting it to Main:
let Main = ViewController()
and here is where I'm attempting to run the function from (force touch quick action from the homescreen):
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "com.Sicarix.Ticket.Add5"{
if(CountCurrent==0){
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+2
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
else{
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+1
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
}
}
however, when I try to use the shortcut it opens the app to a white screen and stays there. Console is spitting out:
fatal error: unexpectedly found nil while unwrapping an Optional value
the code runs fine when I remove the Main.Refresh(), it just doesn't update the labels in my app, hence the need for the function.
Please help, I'm so ready to move on past this bug....
also please bear in mind that I haven't even been coding in swift for a week yet, so please break down what was wrong as best you can. TIA
Change your viewController object
let nextVC = storyboard?.instantiateViewController(withIdentifier:"ViewController") as! ViewController
The problem is that you are instantiating a new ViewController using ViewController() and that ViewController isn't added to your view controller hierarchy.
You need to use your Storyboard to instantiate the ViewController by using let mainVC = UIStoryboard(name: "Main", bundle: "nil").instantiateViewController(withIdentifier:"ViewController") as! ViewController and make sure your identifier is set up in Storyboard.
If you are not using Storyboard, another solution is to store the text you want to display on the UI in data source variables and only update those variables in 'AppDelegate', then load the content of those variables onto your labels in 'viewDidLoad' or 'viewWillAppear'.
I am new to Unit test in Swift. Now I wanted to test my viewController. In viewDidLoad I have an asynchronous call. So that if I want to test my Controller if the data got loaded correctly, the data didn't got loaded. I already read that I have to build in an XCTestExpectation.
So the information I have, I got from this question: XCTest Unit Test data response not set in test after viewDidLoad
The answer there is an example from which I don't know how to implement. My test class looks like this:
import XCTest
#testable import apirequest
class SearchedForViewControllerTests: XCTestCase {
var vc: SearchedForViewController!
override func setUp() {
super.setUp()
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
vc = storyboard.instantiateViewController(withIdentifier: "SearchedForViewController") as! SearchedForViewController
vc.passedString = "garten"
let _ = vc.view
}
func testArticlesShwon() {
print(vc.tableView.numberOfRows(inSection: 0))
}
}
So if I look on my own code the part of viewDidLoad happens in
let _ = vc.view
If I want to build in a Expectation, I have to wait for this part. But the part is not a function. So I don't know how I could tell my Expectation to fulfill after loading.
This may be more of an opinion/design answer, but I would highly recommend testing your ViewController and your Model completely separately.
E.g. when you're testing your VC, manually set the data in your tests, then separately have tests to ensure your model and any networking/async calls are functioning properly.