Swift Delegation: unexpectedly found nil while unwrapping an Optional value - swift3

Edit: Please note the question below discusses using delegation between
2 viewcontrollers that are also implemented in a UITabBarController.
I've done a fair bit of searching here and on YouTube, but haven't seen my issue replicated elsewhere. I'll keep it to the point.
I have 2 view controllers that I coded myself -not generated by XCode-; TabOneController, and TabTwoController
below are the coding for both...
import UIKit
class TabOneController: UIViewController{
private let instanceOfTabOneView = TabOneView()
var vc1Delegate: fromOneToTwo!
override func loadView() {
super.loadView()
view.addSubview(instanceOfTabOneView.buildTheVu())
view.backgroundColor = UIColor.white
runThisOnce()
}
func runThisOnce(){
vc1Delegate.passTheValue(heroNameIs: "pass this to TabTwoController")
}
}
protocol fromOneToTwo{
func passTheValue(heroNameIs: String)
}
as for tab 2...
import UIKit
class TabTwoController: UIViewController, fromOneToTwo{
private let instanceOfTabTwoView = TabTwoView()
override func loadView() {
super.loadView()
view.addSubview(instanceOfTabTwoView.buildTheVu())
assignDelegateToSelf()
}
func assignDelegateToSelf(){
let instanceTabOne = TabOneController()
instanceTabOne.vc1Delegate = self
}
func passTheValue(heroNameIs:String){
instanceOfTabTwoView.txtFld.text = heroNameIs
}
}
I'm getting the following error at runtime -the app builds successfully-...
fatal error: unexpectedly found nil while unwrapping an Optional value
on the following line...
vc1Delegate.passTheValue(heroNameIs: "pass this to TabTwoController")
When I comment out the above line, the app builds and runs successfully, but of course the app doesn't execute the delegation.
I kinda understand what the compiler is trying to tell me, that the
vc1Delegate
hasn't been instantiated -I guess-. But I searched under every rock, and can't seem to find how to get around this.
I'd appreciate any help or guidance. Sorry if my code seems immature, I'm new to Swift and programming in general. Thank you.

In a UITabBarController, the first tab is instantiating by default. The view controller initialization executes the loadView and finds a nil because the second tab did not initialize yet; this is normal behavior. My suggestion is making the delegate weak optional with the ? suffix and run the delegate code elsewhere. Also, always capitalize the first letter in class and protocol names.
weak var vc1Delegate: FromOneToTwo?
If this structure is mandatory, try with a custom notification observer instead.

First thing first, your error happens in line var vc1Delegate: fromOneToTwo! while you declared this delegate variable as not null but then calling passTheValue on it. A correct practice will be
var vc1Delegate: fromOneToTwo?
func runThisOnce(){
if let delegate = vc1Delegate {
vc1Delegate.passTheValue(heroNameIs: "pass this to TabTwoController")
}
}
Secondly, you are not using delegate correctly. In the assignDelegateToSelf() function, you are creating a new instance of TabOneController
and then assign delegate. Instead, you need to find out the existing TabOneController instance and assign delegate.

I try this and worked add delegate = self in table cellforRowAt
like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "ChapterPracticeTableViewCell") as! ChapterPracticeTableViewCell
cell.deligate = self
return cell
}

Related

What is the difference between `FileDocument` and `ReferenceFileDocument` for `DocumentGroups` in SwiftUI?

I'm trying to setup a DocumentGroup in my app, but there's no examples out there yet ReferenceFileDocument is for. I know what a FileDocument is, but how are ReferenceFileDocuments different.
In the docs all it says is:
Conformance to ReferenceFileDocument is expected to be thread-safe,
and deserialization and serialization will be done on a background
thread.
There's a hint in the name: ReferenceFileDocument is a document that's a reference type (ie, a class). FileDocument is for a struct based document.
This has an effect on how documents are saved because SwiftUI can just make a copy of the reference type and save it without worrying about you coming along and modifying it during the save, since it's a value type or tree of value types.
With ReferenceFileDocument, there also doesn't seem to be a clear way for the SwiftUI to know when to save, so it depends on you telling it. There's no direct "doc is dirty, save it now" method, so the way you inform SwiftUI that you've done something that requires saving is through the undo manager.
You also need to provide a snapshot method to return a copy of the document that's safe for it to save.
final class QuizDocument: ReferenceFileDocument, ObservableObject {
#Published var quiz: QuizTemplate
init(quiz: QuizTemplate) {
self.quiz = quiz
}
static var readableContentTypes: [UTType] { [.exampleText] }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let quiz = try? JSONDecoder().decode(QuizTemplate.self, from: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.quiz = quiz
}
// Produce a snapshot suitable for saving. Copy any nested references so they don't
// change while the save is in progress.
func snapshot(contentType: UTType) throws -> QuizTemplate {
return self.quiz
}
// Save the snapshot
func fileWrapper(snapshot: QuizTemplate, configuration: WriteConfiguration) throws -> FileWrapper {
let data = try JSONEncoder().encode(quiz)
return .init(regularFileWithContents: data)
}
}
ReferenceFileDocument is a document type that will auto-save in the background. It is notified of changes via the UndoManager, so in order to use it you must also make your document undo-able.
The only mention I see of it in the docs is here.
Here is a working example.

SwiftUI ForEach does not compile with if block inside - Bug or am I doing something wrong?

I am getting an error that I don't understand. I am not sure if it is a compiler error or if I am doing something wrong?
Inside a swiftUI View I have a list showing elements from core data (Figure 1). In the example below I replaced the t.name with "yo" for some undefined reason 😅.
Anyway, the tasks is a fetch request from Core Data:
#FetchRequest(entity: Task.entity(), sortDescriptors: []) var tasks: FetchedResults<Task>
FIGURE 1: Works fine to build and run the app.
FIGURE 2: Does not work to build and run the app.
Please help me understand what I am doing wrong or is this a compiler bug? Why can't I add the if block inside the ForEach? I can provide more information if needed. Thanks!
You can use if inside ForEach, but you should remember that ForEach is not language operator foreach but a struct type with ViewBuilder in constructor with generics in declaration, so it needs to determine type, which in your case it cannot determine.
The possible solution is to tell explicitly which type you return, as below (tested with Xcode 11.2 / iOS 13.2)
ForEach(tasks, id: \.id) { name -> Text in
if (true) {
return Text("name")
}
}
You have to return nil in case of a false condition. So you need to declare parameter as Optional and return nil in case of a false condition (XCode - 11.3.1).
ForEach(tasks, id: \.id) { t -> Text? in
return condition ? Text("text") : nil
}
}
For some reason, in the ForEach.init you're using, the view building closure isn't annotated with #ViewBuilder. This means that the if/else you're using is Swift's own if statement, not the SwiftUI construct which returns a _ConditionalContent.
I don't know if it's considered a bug by Apple, but I would definitely consider it to be one.
The easiest workaround is just to wrap the if/else in a Group - the Group.init is a #ViewBuilder, so will handle the if/else correctly.
What also worked for me was using a Group inside the ForEach like this:
ForEach(self.items, id: \.self { item in
Group {
if item.id == 0 {
Text(String(item.id))
}
}
}

how i can change title with RX index

I want to change title with an observable int.
in view Model
var index = Variable<Int>(0)
in view Controller
let title = ["title1","title2","title3","title4","title5"]
override func viewWillAppear(_ animated: Bool) {
self.viewModel.index.value = 0
self.viewModel.index
.asObservable()
.map( {self.periodText[$0]
})
.bind(to: self.titleLabel.rx.text)
.addDisposableTo(self.disposeBag)
}
When i do this i have an error in the blind(to) :
fatal error: unexpectedly found nil while unwrapping an Optional value
The function never pass in the .map
How i can change the title when my index change in RX Swift?
The textField on the storyBoard doesn't connect to the #IBOutlet titleLabel which
results in this error message.
My guess is your self.periodText array does not have as many elements as your index value, thus running out of bounds.
Alternatively, it could be that you're defining your diposeBag as self.disposeBag: DisposeBag!. If you've then forgotten to create an instantiation of it before viewWillAppear, it is still set to nil, in which case you'll run into this error too.
Generally, it is best to share more details on where your error occurs, so it is easier to narrow it down.

UIPanGestureRecognizer action method not called in Swift 3

I have set a pan gesture to an imageView, though it's action method never being called in swift 3.
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:)))
panGesture.minimumNumberOfTouches = 1
imageview.addGestureRecognizer(panGesture)
the action method:
#objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {
}
Am I missing anything?
Ok, seems dumb, but after debugging I've noticed that the imageView's userInteractionEnabled is set to false.
After adding this line imageview.isUserInteractionEnabled = true everything seems to be working properly.
You may try to use a function handler for you rUIPanGestureRecognizer like this.
func panGestureHandler(recognizer: UIPanGestureRecognizer) {
}

Creating mock cloud kit objects for unit testing

Trying to mock CKDatabase but it has no accessible initializer. Is there another way to create a fake CKDatabase object?
This code comes back with the "error: cannot override 'init' which has been marked unavailable"
class MockDatabase : CKDatabase
{
override func saveRecord(record: CKRecord!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecordID!, NSError!) -> Void)!) { }
override func fetchRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteSubscriptionWithID(subscriptionID: String!, completionHandler: ((String!, NSError!) -> Void)!) { }
override func saveSubscription(subscription: CKSubscription!, completionHandler: ((CKSubscription!, NSError!) -> Void)!) {}
}
The cloud kit framework seems like a perfect candidate for unit testing as there are a lot of possible errors that can come back from the server and making sure your app can handle them all consistently , with every iteration would be great. Has anyone found a way around this?
Since CKDatabase init is made unavailable, you will never be able to create an instance of it. So there is no point in creating your MockDatabase because you will never be able to create an instance of that. The only way to do some sort of Mocking is by creating your own DAO class wrapper around the CKDatabase functionality and only call CKDatabase functions using that DAO. Then you could mock that DAO. You could even add extra functionality to that DAO, but then you will not be able to test that.
All the CloudKit classes are #objc (they all derive from NSObject.) That means they use dynamic dispatching. In Objective-C you could mock CKDatabase by implementing a new Obj-C class MockCKDatabase that subclasses NSObject and implements all the public methods of CKDatabase (or at least all the ones you use.) Then you literally just instantiate one and cast the pointer from MockCKDatabase* to CKDatabase*.
I've never done this sort of thing in Swift, but I imagine that if you just did the same steps it should work.
as #Edwin pointed out, CKDatabase init is made unavailable, so you can't make instances of it. Neither can you subclass or use an extension to override CKContainer init (compiler will complain that 'init()' is unavailable). You can't even use a CKContainer with a mock of CKDatabase, since the logic that associates them is hidden within the CloudKit implementation. All that said, I built a framework called MockCloudKitFramework that is a drop-in replacement for a limited (but reasonable) subset of CloudKit capabilities. The MockCloudKitFramework (MCF) implements mock operations for CKContainer and CKDatabase, plus the main subclasses of CKDatabaseOperation.
MCF creates its own protocols (that contain the same signatures as their CloudKit counterparts) and extends both CloudKit and MCF mocks from this set of protocols. It was necessary to create these protocols since CKContainer and CKDatabase both inherit directly from NSObject as their common Protocol. So that IOC would force generic functions to be open to NSObject types - leaving functions wide open for injection of everything that inherits from NSObject.
So using MCF comes down to:
Extending CloudKit and MCF classes from these protocols.
Refactoring your code to use generics - typed on the MCF protocols.
Using IOC to inject the mocks of CKContainer and CKDatabase (MockCKContainer and MockCKDatabase, respectively).
See the MCF docs - examples are provided, along with unit and integration tests.
Using MCF
Lets say that you have a generic class that takes a type based on the CKContainerProtocol protocol (one of the MCF protocols). It contains a method that accepts either a CKFetchRecordsOperation or a MockCKFetchRecordsOperation (the MCF mock of CKFetchRecordsOperation) based on their common MCF protocol, CKFetchRecordsOperationProtocol:
import CloudKit
/// Example Class to handle iCloud related transactions.
class CloudController<T: CKContainerProtocol> {
let cloudContainer: T
let database: T.DatabaseType
init(container: T, databaseScope: CKDatabase.Scope) {
self.cloudContainer = container
self.database = container.database(with: databaseScope)
}
/// Check if a record exists in iCloud.
/// - Parameters:
/// - cKFetchRecordsOperation: the fetch operation
/// - recordId: the record id to locate
/// - completion: closure to execute on caller
/// - Returns: success(true) when record is located, success(false) when record is not found, failure if an error occurred.
func checkCloudRecordExists<O: CKFetchRecordsOperationProtocol> (
cKFetchRecordsOperation: O,
recordId: CKRecord.ID,
_ completion: #escaping (Result<Bool, Error>) -> Void) {
var dbOperation = cKFetchRecordsOperation
dbOperation.recordIDs = [recordId]
var record: CKRecord?
dbOperation.desiredKeys = ["recordID"]
// perRecordResultBlock doesn't get called if the record doesn't exist
dbOperation.perRecordResultBlock = { _, result in
// success iff no partial failure
switch result {
case .success(let r):
record = r
case .failure:
record = nil
}
}
// fetchRecordsResultBlock always gets called when finished processing.
dbOperation.fetchRecordsResultBlock = { result in
// success if no transaction error
switch result {
case .success:
if let _ = record { // record exists and no errors
completion(.success(true))
} else { // record does not exist
completion(.success(false))
}
case .failure(let error): // either transaction or partial failure occurred
completion(.failure(error))
}
}
database.add(dbOperation)
}
}
We can build a test without MCF that validates that the record does (or doesn't) exist, but first we have to:
Populate (or depopulate) the CloudKit test environment with the data to set the test up.
Make sure that we are using the same CKDatabase scope (public, private, or shared) as where the data is.
Hope that there are no network hiccups or data transfer errors that will inject unanticipated errors or interruptions.
But still, we can't test what the error conditions might be since we have no control over them.
MCF allows us to test all of this, using a local mock instance of CKDatabase (MockCKDatabase) that we can manipulate directly or through CloudKit modification operations like (Mock)CKModifyRecordsOperation. MockCKContainer is used to access the MockCKDatabase instance of the appropriate scope using the same accessors as CKContainer does. Here is an example of a test case setup:
import XCTest
import CloudKit
#testable import OurProject // required for access to CloudController
import MockCloudKitFramework // import MCF only into test environment
class MockCloudKitTestProjectIntegrationTest: XCTestCase {
var cloudContainer: MockCKContainer!
var cloudDatabase: MockCKDatabase!
// NOTE that CloudController class is a Generic and that both CKContainer and MockCKContainer extend CKContainerProtocol
var cloudController: CloudController<MockCKContainer>!
// called before each test
override func setUpWithError() throws {
try? super.setUpWithError()
// reset state for each test. Each test must set up exactly the state they require.
MockCKContainer.resetContainer()
// get the default container from MCF
cloudContainer = MockCKContainer.default()
// get the MockCKDatabase of CKDatabase.Scope of private
cloudDatabase = cloudContainer.privateCloudDatabase
// we pass in a MockCKContainer to the generic function expecting a class that extends MCF CKContainerProtocol
cloudController = CloudController(container: cloudContainer, databaseScope: .private)
}
}
Now we can test the checkCloudRecordExists() method by adding the record that we expect to fetch directly to MockCKDatabase and calling the function, passing in our mock CKFetchRecordsOperation:
func test_checkCloudRecordExists_success() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord() // function that makes a CKRecord
// add the record to MockCKDatabase
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// pass in our mock CKFetchRecordsOperation
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { exists in
XCTAssertTrue(exists)
expect.fulfill()
}
waitForExpectations(timeout: 1)
}
If we wanted to test an error condition from the call, we just need to add an error using the setError property of MockCKDatabaseOperation (or any of its subclasses). Now the test will fail, even though the record exists. This simulates an internal error that was the result of a CloudKit transaction:
// call checkCloudRecordExists() when the record is present but error is set
func test_checkCloudRecordExists_error() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
let nsErr = createNSError(with: CKError.Code.internalError)
operation.setError = nsErr
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
expect.fulfill()
case .failure(let error):
XCTAssertEqual(error.createCKError().code.rawValue, nsErr.code)
expect.fulfill()
}
}
waitForExpectations(timeout: 1)
}
We can even test our function logic for partial failures to make sure that we handle the scenario of when a record might be found but some CKError occurred so we cannot be sure. All we need to do is set the setRecordErrors property on the operation to the set of record ids that should fail (MCF picks a random CKError to fail with):
// test for partial failures
func test_checkCloudRecordExists_partial_failure() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
operation.setRecordErrors = [record.recordID]
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
case .failure(let error):
let ckError = error.createCKError()
XCTAssertEqual(ckError.code.rawValue,
CKError.partialFailure.rawValue,
"The transaction error should always be set to CKError.partialFailure when record errors occur")
if let partialErr: NSDictionary = error.createCKError().getPartialErrors() {
let ckErr = partialErr.allValues.first as? CKError
XCTAssertEqual("CKErrorDomain", ckErr?.toNSError().domain)
expect.fulfill()
}
}
}
waitForExpectations(timeout: 1)
}