function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Teo PopaTeo Popa 

Build a Mobile Insurance App with Swift and the Salesforce Mobile SDK for iOS

I'm trying to complete the "Build a Mobile Insurance App with Swift and the Salesforce Mobile SDK for iOS" trailhead but I'm stuck. I've installed the package in my Trailhead Playground, and created the XCode project from the repository link. I refactored the NewClaimViewController+Uploading.swift file like they say to to call uploadPhotos and uploadAudio asynchronously (code pasted in comment) and added my playground domain to the app login. When I log into the app from the simulator, i get the message "Server Error Can't connect to the server: An SSL error has occurred and a secure connection to the server cannot be made." If I click OK, it moves forward with the sign in but the Claims landing screen loader spins endlessly. I can click the plus sign to make a claim, but again when I hit submit, again I get an endless spinner and the record is not created in the playground.
Best Answer chosen by Teo Popa
Teo PopaTeo Popa
Finally, figured what was going on here. The code was correct, it was purely a connectivity issue: 1) https:// was being included in the URL when it should not have been (the trailhead specifically calls this out), 2) the two-factor authentication was interfering because of failed login attempts resulting from incorrect URL (would not send a code anymore, needed to create a new trailhead playground, and 3) the office network was interfering so needed to try another network.

All Answers

Teo PopaTeo Popa
NewClaimViewController+Uploading.swift
import UIKit
import MapKit

import SalesforceSDKCore

// Extends `NewClaimViewController` with methods for submitting the claim details
// to the server.
//
// Each of these methods, starting with `uploadClaimTransaction()`, executes an
// asynchronous request, and in the completion handler for the request, calls
// the next method in the chain.
//
// The majority of the `RestClient` methods being called are from an extension
// in RestClient+NewClaim.swift.
extension NewClaimViewController {

       /// Logs the given error.
       ///
       /// This project doesn't do any sophisticated error checking, and simply
       /// uses this as the failure handler for `RestClient` requests. In a real-world
       /// application, be sure to replace this with information presented to the user
       /// that can be acted on.
       ///
       /// - Parameters:
       ///   - error: The error to be logged.
       ///   - urlResponse: Ignored; this argument provides compatibility with
       ///     the `SFRestFailBlock` API.
       private func handleError(_ error: Error?, urlResponse: URLResponse? = nil) {
              let errorDescription: String
              if let error = error {
                     errorDescription = "\(error)"
              } else {
                     errorDescription = "An unknown error occurred."
              }
             
              if(alert.isViewLoaded){
                     alert.dismiss(animated: true)
              }
              alert = UIAlertController(title: "We're sorry, an error has occured. This claim has not been saved.", message: errorDescription, preferredStyle: .alert)
              alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: { action in self.unwindToClaims(forCaseID: nil)}))
              present(self.alert, animated: true)
             
              SalesforceLogger.e(type(of: self), message: "Failed to successfully complete the REST request. \(errorDescription)")
       }

       /// Begins the process of uploading the claim details to the server.
       func uploadClaimTransaction() {
              SalesforceLogger.d(type(of: self), message: "Starting transaction")

              alert = UIAlertController(title: nil, message: "Submitting Claim", preferredStyle: .alert)
              let loadingModal = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
              loadingModal.hidesWhenStopped = true
              loadingModal.style = .gray
              loadingModal.startAnimating()
              alert.view.addSubview(loadingModal)
              present(alert, animated: true, completion: nil)

              RestClient.shared.fetchMasterAccountForUser(onFailure: handleError) { masterAccountID in
                     SalesforceLogger.d(type(of: self), message: "Completed fetching the Master account ID: \(masterAccountID). Starting to create case.")
                     self.createCase(withAccountID: masterAccountID)
              }
       }

       /// Creates a new Case record from the transcribed text and map location.
       /// When complete, `createContacts(relatingToAccountID:forCaseID:)` is called.
       ///
       /// - Parameter accountID: The ID of the account with which the case is
       ///   to be associated.
       private func createCase(withAccountID accountID: String) {
              let dateFormatter = DateFormatter()
              dateFormatter.dateStyle = .full

              var record = [String: Any]()
              record["origin"] = "Redwoods Car Insurance Mobile App"
              record["status"] = "new"
              record["accountId"] = accountID
              record["subject"] = "Incident on \(dateFormatter.string(from: Date()))"
              record["description"] = self.transcribedText
              record["type"] = "Car Insurance"
              record["Reason"] = "Vehicle Incident"
              record["Incident_Location_Txt__c"] = self.geoCodedAddressText
              record["Incident_Location__latitude__s"] = self.mapView.centerCoordinate.latitude
              record["Incident_Location__longitude__s"] = self.mapView.centerCoordinate.longitude
             
              RestClient.shared.createCase(withFields: record, onFailure: handleError) { newCaseID in
                     SalesforceLogger.d(type(of: self), message: "Completed creating case with ID: \(newCaseID). Uploading Contacts.")
                     self.createContacts(relatingToAccountID: accountID, forCaseID: newCaseID)
              }
       }

       /// Creates Contact records for each of the contacts that the user added.
       /// When complete, `createCaseContacts(withContactIDs:forCaseID:)` is called.
       ///
       /// - Parameters:
       ///   - accountID: The ID of the account with which the contact records are
       ///     to be associated.
       ///   - caseID: The ID of the case that is being modified.
       private func createContacts(relatingToAccountID accountID: String, forCaseID caseID: String) {
              let contactsRequest = RestClient.shared.compositeRequestForCreatingContacts(from: contacts, relatingToAccountID: accountID)
              RestClient.shared.sendCompositeRequest(contactsRequest, onFailure: handleError) { contactIDs in
                     SalesforceLogger.d(type(of: self), message: "Completed creating \(self.contacts.count) contact(s). Creating case<->contact junction object records.")
                     self.createCaseContacts(withContactIDs: contactIDs, forCaseID: caseID)
              }
       }

       /// Associates the given Contact record IDs with the case.
       /// When complete, `uploadMapImage(forCaseID:)` is called.
       ///
       /// - Parameters:
       ///   - contactIDs: The IDs of the Contact records being associated with the case.
       ///   - caseID: The ID of the case that is being modified.
       private func createCaseContacts(withContactIDs contactIDs: [String], forCaseID caseID: String) {
              let associationRequest =
                     RestClient.shared.compositeRequestForCreatingAssociations(fromContactIDs: contactIDs, toCaseID: caseID)
              RestClient.shared.sendCompositeRequest(associationRequest, onFailure: handleError) { _ in
                     SalesforceLogger.d(type(of: self), message: "Completed creating \(contactIDs.count) case contact record(s). Optionally uploading map image as attachment.")
                     self.uploadPhotos(forCaseID: caseID)
              }
       }

       /// Generates a snapshot image of the map view and uploads it as an attachment.
       /// When complete, `uploadPhotos(forCaseID:)` is called.
       ///
       /// - Parameter caseID: The ID of the case that is being modified.
       private func uploadMapImage(forCaseID caseID: String) {
              let options = MKMapSnapshotter.Options()
              let region = MKCoordinateRegion.init(center: mapView.centerCoordinate, latitudinalMeters: regionRadius, longitudinalMeters: regionRadius)
              options.region = region
              options.scale = UIScreen.main.scale
              options.size = CGSize(width: 800, height: 800)
              options.mapType = .standard

              let snapshotter = MKMapSnapshotter(options: options)
              snapshotter.start { snapshot, error in
                     guard let snapshot = snapshot, error == nil else {
                           return
                     }
                     UIGraphicsBeginImageContextWithOptions(options.size, true, 0)
                     snapshot.image.draw(at: .zero)

                     let pinView = MKPinAnnotationView(annotation: nil, reuseIdentifier: nil)
                     let pinImage = pinView.image

                     var point = snapshot.point(for: self.mapView.centerCoordinate)
                     let pinCenterOffset = pinView.centerOffset
                     point.x -= pinView.bounds.size.width / 2
                     point.y -= pinView.bounds.size.height / 2
                     point.x += pinCenterOffset.x
                     point.y += pinCenterOffset.y
                     pinImage?.draw(at: point)

                     let mapImage = UIGraphicsGetImageFromCurrentImageContext()!
                    
                     //Insert your attachmentRequest here.
                    
                     //End area for Attachment Request.
                     UIGraphicsEndImageContext()
             
                     //Use your attachment request here.
              }
       }

       /// Uploads each photo as an attachment.
       /// When complete, `uploadAudio(forCaseID:)` is called.
       ///
       /// - Parameter caseID: The ID of the case that is being modified.
       private func uploadPhotos(forCaseID caseID: String) {
              for (index, img) in self.selectedImages.enumerated() {
                     let attachmentRequest =
                           RestClient.shared.requestForCreatingImageAttachment(from: img,
                                                                                                                     relatingToCaseID: caseID)
                     RestClient.shared.send(request: attachmentRequest,
                                                         onFailure: self.handleError){ result, _ in
                                                              SalesforceLogger.d(type(of: self), message: "Completed upload of photo \(index + 1) of\(self.selectedImages.count).")
                     }
              }
              self.uploadAudio(forCaseID: caseID)
       }

       /// Uploads the recorded audio as an attachment.
       /// When complete, `showConfirmation()` is called.
       ///
       /// - Parameter caseID: The ID of the case that is being modified.
       private func uploadAudio(forCaseID caseID: String) {
              if let audioData = audioFileAsData() {
                     let attachmentRequest =
                           RestClient.shared.requestForCreatingAudioAttachment(from: audioData,
                                                                                                                     relatingToCaseID: caseID)
                     RestClient.shared.send(request: attachmentRequest,
                                                         onFailure: handleError) { _, _ in
                                                              SalesforceLogger.d(type(of: self), message:
                                                                     "Completed uploading audio file. Transaction complete!")
                                                              self.unwindToClaims(forCaseID: caseID)
                     }
              } else {
                     // Complete upload if there is no audio file.
                     SalesforceLogger.d(type(of: self), message: "No audio file to upload. Transaction complete!")
                           self.unwindToClaims(forCaseID: caseID)
              }
       }

       /// Dismisses the current modal and returns the user to open claims.
       private func unwindToClaims(forCaseID caseID: String?) {
              wasSubmitted = true
              // Unwind back to claims. UI calls must be performed on the main thread.
              if let cid = caseID {
                     let commentRequest = RestClient.shared.requestForCreate(withObjectType: "CaseComment", fields: ["parentId": cid, "commentBody": "navigating back to claims list view"])
                     RestClient.shared.send(request:commentRequest, onFailure: handleError) {_,_ in
                           SalesforceLogger.d(type(of: self), message: "Completed writing case comment")
                     }
              }
             
              DispatchQueue.main.async {
                     self.performSegue(withIdentifier: "unwindFromNewClaim", sender: self)
              }
       }
}
Teo PopaTeo Popa
Finally, figured what was going on here. The code was correct, it was purely a connectivity issue: 1) https:// was being included in the URL when it should not have been (the trailhead specifically calls this out), 2) the two-factor authentication was interfering because of failed login attempts resulting from incorrect URL (would not send a code anymore, needed to create a new trailhead playground, and 3) the office network was interfering so needed to try another network.
This was selected as the best answer
Teo PopaTeo Popa
The build was successful after addressing all of these issues.