Workaround for EKParcipant URL accessing crash? - swift

Some of my users have been sent me logs identifying a EXC_BREAKPOINT (SIGTRAP) Error on this line of code. I've been been trying to make it safe but all of the properties of EKParticipant are non optional so comparing to nil just gives me a warning saying it will always be true. If something is nil here how should I handle it?
Error Line
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
Apple Error Description
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an
attached debugger the chance to interrupt the process at a specific
point in its execution. You can trigger this exception from your own
code using the __builtin_trap() function. If no debugger is attached,
the process is terminated and a crash report is generated. Lower-level
libraries (e.g, libdispatch) will trap the process upon encountering a
fatal error. Additional information about the error can be found in
the Additional Diagnostic Information section of the crash report, or
in the device's console. Swift code will terminate with this exception
type if an unexpected condition is encountered at runtime such as:
a non-optional type with a nil value
a failed forced type conversion Look at the Backtraces to determine where the unexpected condition was encountered. Additional
information may have also been logged to the device's console. You
should modify the code at the crashing location to gracefully handle
the runtime failure. For example, use Optional Binding instead of
force unwrapping an optional."
Full Method
/**
Parses participants for a given event.
Goes through the EKEvents attendees array to build Attendee objects used to model a participant.
- parameter event: The calendar event we'll be finding the participants for.
- returns: An array of Attendee objects with the participants name, email, required/optional status and whether they've accepted their invitation to the event.
*/
private static func parseParticipantsIn(event: EKEvent) -> [Attendee] {
var participants = [Attendee]()
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
let participantName : String? = parse(EKParticipantName: participant)
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
guard (participantName != nil && participantEmail != nil)
else
{
log.error("Participant could not be parsed")
continue
}
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
return participants
}

This appears to be a problem with the EKParticipant.url property. Any attempted access of EKParticipant.url causes a crash if you have a participant with the following email field within it.
"Bill Gates" <billgates#google.com>
I'd guess the quotation marks end the String prematurely. It is fine when accessed from EKParticipant.description so I intend to parse it from there.

This is a ridiculous issue, and Deco pinpointed it exactly. I used a different approach to get around it though: Since I'm already working in a mixed code base (obj-c and Swift), I created a class method on one of my obj-c classes that takes an EKParticipant and returns its URL as a string. Then, in Swift, I call that class method to get the URL instead of directly accessing the property (and crashing). It's hacky, but better than crashing and saved me from parsing the description.

This is rather old question but yet I hit this issue myself. My solution is to fallback to ObjC in order to workaround it.
Just add this ObjC functions to swift bridging header file and you are good to use them in swift.
static inline BOOL
participantHasNonNilURL (EKParticipant* _Nonnull participant) {
return participant.URL != nil;
}
static inline NSURL* _Nullable
participantURL(EKParticipant* _Nonnull participant) {
if (participant.URL != nil) {
return participant.URL;
}else {
return nil;
}
}
Example of usage:
extension EKParticipant {
var optionalURL: URL? {
return participantURL(self)
}
var hasURL: Bool {
return participantHasNonNilURL(self)
}
}

This is still an issue on macOS 11.2... I have reported it to Apple. I encourage anyone else hitting this issue to do the same.
The only Swift-only workaround that worked for me is:
extension EKParticipant {
public var safeURL: URL? {
perform(#selector(getter: EKParticipant.url))?.takeUnretainedValue() as? NSURL? as? URL
}
}

Validations are added incorrectly, please check the below response about how the guard could be used.
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
guard let participantName : String? = parse(EKParticipantName: participant) else{
log.error("error in participant name")
return
}
guard let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "") else{
log.error("error in participant email")
return
}
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
/* guard validation is not required here */
if (participantName != nil && participantEmail != nil){
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
}
return participants

Related

Swift Firebase Authentication - two questions about error handling (I'm not sure how to name these errors)

It's really hard to find a proper title for this question. Please be easy on me.
The first part is a check to see if an account exists:
Auth.auth().fetchSignInMethods(forEmail: userEmail, completion: {
(providers, error) in
if error != nil {
self.displayAlertMessage(alertTitle: "Unhandled error", alertMessage: "Undefined error #SignUpViewController_0001");
return;
} else if providers != nil {
self.displayAlertMessage(alertTitle: "Error", alertMessage: "This account is not exist.");
return;
}
})
As you can see, I have something named Unhandled error with message Undefined error. I don't know how to name it properly. Can somebody explain that part to me?
The second one is about getting a localized string - any ideas to make it fancy?
Auth.auth().createUser(withEmail: userEmail, password: userPassword) { user, error in if error == nil && user != nil {
self.displayAlertMessage(alertTitle: "Success", alertMessage: "Your account created successfully. We send you a verification email.", dismiss: true);
} else {
self.displayAlertMessage(alertTitle: "Firebase error", alertMessage: "(error!.localizedDescription)");
}
}
Thanks for tips :)
You can handle the Errors this way:
Auth.auth().fetchSignInMethods(forEmail: email, completion: { (response, error) in
if let error = error, let errCode = AuthErrorCode(rawValue: error._code)
{
switch errCode {
case .emailAlreadyInUse:
GeneralHelper.sharedInstance.displayAlertMessage(titleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), messageStr: LocalizeConstant.CommonTitles.Continue.rawValue.localizedStr())
case .accountExistsWithDifferentCredential:
GeneralHelper.sharedInstance.displayAlertMessage(titleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), messageStr: LocalizeConstant.CommonTitles.Continue.rawValue.localizedStr())
default:
break
}
return
}
}
Here I am getting the errCode using AuthErrorCode provided by Firebase itself and then, I am passing in the received error code using error._code. So, now I can get the type of AuthErrorCode. Using this I am making cases like .emailAlreadyInUser, .accountExistsWithDifferentCredential etc. You can just type . and it will show you all the AuthErrorCodes. So, you can simply handle the error codes in this way.
Now, coming to the second part of the question, i.e. getting localized string. You can add localization to Firebase, for that you have to select the language code. Auth.auth().languageCode = "en" //For English. But, I do not think that it gives localized errors as there are many more languages than what Firebase supports. This mainly for sending localized emails.
To handle the localization, you have to create your own method as I did. You can see that I have called a function displayAlertMessage in which I am passing thetitleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), which is a part of localization.
struct LocalizeConstant {
enum CommonTitles: String
{
case Alert = "common_alert"
}
}
This value designates to the key given by me in the localization file. If you do not know about localization, you have to do a Google search on it. Let's say I have two Localizable.strings one is in English and the other one is in French. In Localizable.strings(English), I've written Alert like this:
"common_alert" = "Alert";
And, In French:
"common_alert" = "Alerte!";
So, this is how I have manually added localization in my app. But, to achieve this you have to do two things. 1) You have to set up your appLanguage. 2) You have to call a method which will fetch the values from these keys defined in the Localizable.strings file.
To do this, I have created a method localizedStr(). It is an extension to String and you can use it as follows.
extension String{
func localizedStr() -> String
{
var finalRes = ""
if let path = Bundle.main.path(forResource: Constants.appLang, ofType: "lproj") //Constants.appLang is "en" here for "English", but you can set accordingly.
{
if let bundle = Bundle(path: path)
{
finalRes = NSLocalizedString(self, tableName: nil, bundle: bundle, value: " ", comment: " ")
}
}
return finalRes
}
}
Now, this method localizedStr() will give you a localized string according to your app language. Even, if Firebase provides localized error codes(which I think it does not), it is impossible to get the error description in each language. So this is the best way I came up with. It may not be the best method out there in the world, but it does the task.
P.S.: To optimize this throughout the app, either you can create an extension to AuthErrorCode or you can create a Helper function where you will just pass the error._code and it will return the localized string. I've added the long way so that you can understand everything in the best way.

Ambiguous reference to member 'save(_:completionHandler:)' with CloudKit save attempt

I'm trying to save back to CloudKit after updating a reference list and getting the error on the first line of this code block.
Error: Ambiguous reference to member 'save(_:completionHandler:)'
CKContainer.default().publicCloudDatabase.save(establishment) { [unowned self] record, error in
DispatchQueue.main.async {
if let error = error {
print("error handling to come")
} else {
print("success")
}
}
}
This sits within a function where the user going to follow a given location (Establishment). We're taking the existing establishment, and its record of followers, checking to see if the selected user is in it, and appending them to the list if not (or creating it if the list of followers is null).
Edit, in case helpful
//Both of these are passed in from the prior view controller
var establishment: Establishment?
var loggedInUserID: String?
#objc func addTapped() {
// in here, we want to take the logged in user's ID and append it to the list of people that want to follow this establishment
// which is a CK Record Reference
let userID = CKRecord.ID(recordName: loggedInUserID!)
var establishmentTemp: Establishment? = establishment
var followers: [CKRecord.Reference]? = establishmentTemp?.followers
let reference = CKRecord.Reference(recordID: userID, action: CKRecord_Reference_Action.none)
if followers != nil {
if !followers!.contains(reference) {
establishmentTemp?.followers?.append(reference)
}
} else {
followers = [reference]
establishmentTemp?.followers = followers
establishment = establishmentTemp
}
[this is where the CKContainer.default.....save block pasted at the top of the question comes in]
I've looked through the various posts on 'ambiguous reference' but haven't been able to figure out the source of my issue. tried to explicitly set the types for establisthmentTemp and followers in case that was the issue (based on the solutions to other related posts) but no luck.
Afraid I'm out of ideas as a relatively inexperienced newbie!
Help appreciated.
Documenting the solution that I figured out:
Combination of two issues:
I was trying to save an updated version of a CK Record instead of updating
I was not passing a CK Record to the save() call - but a custom object
(I believe point two was the cause of the 'ambiguous reference to member'
error)
I solved it by replacing the save attempt (first block of code in the question) with:
//first get the record ID for the current establishment that is to be updated
let establishmentRecordID = establishment?.id
//then fetch the item from CK
CKContainer.default().publicCloudDatabase.fetch(withRecordID: establishmentRecordID!) { updatedRecord, error in
if let error = error {
print("error handling to come")
} else {
//then update the 'people' array with the revised one
updatedRecord!.setObject(followers as __CKRecordObjCValue?, forKey: "people")
//then save it
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in
}
}
}

How to fix error when looking for child in firebase database (Swift)?

I am trying to save items to a Firebase database under a child taken from a text field. I haven't got to that yet though, I am still trying to work out how to check if a value already exists. Here is my code...
#objc func submitNote() {
print("Attempting to save or update note")
//Check if file exists
if name != nil && text != nil {
//Access database
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("lists").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(self.name!){
print("true")
//Update
return
} else {
print("false")
//Create
self.uploadNew()
}
})
return
} else {
print("Error")
return
}
}
It has to be an #objc function as it is run using #selector in a menu bar. As you can see it checks to make sure the text fields aren't empty. Here are the variables...
public var name: String?
public var text: String?
Which are set inside separate functions...
name = titleText.text
text = textView.text
The reason I have set the variables this way is because I am creating the view programically and both individual text views/fields are set inside functions so I don't think I can access them from the submitNote function. Anyway the error I'm getting it this...
*** Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(hasChild:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''
I have checked and triple checked but there aren't any of those values in the text fields and even though the if statement is meant to stop the app continuing unless they are not equal to nil I tried with text in the fields but no matter what if I press the button to run this function I get the same error. Does anyone know how to fix it?
You only check if name is not null but not if it is empty ("")
if let value = self.name, !value.isEmpty...
Your Firebase structure was not included in the question so I'll make one up for you
lists
Leroy: true
Billy: true
James: true
now some code to see if a name exists in the lists node. We're using snapshot.exists to see if any data was returned in the snapshot.
func doesNameExistInLists(name: String) {
let ref = self.ref.child("lists").child(name)
ref.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("found it")
} else {
print("name not found")
}
})
}
then when it's called, here are the results
self.doesNameExistInLists(name: "Billy")
console: found it
self.doesNameExistInLists(name: "aaaa")
console: name not found

In Swift can you trap "fatal error unexpectedly found nil while unwrapping an optional value"?

I'm parsing an NSDictionary. I don't want to go thru item by item to make sure everything is really there and nothing is unexpectedly nil. I figured I'd just do-try-catch. But I'm getting a compiler warning. Here's my code: saying:
do {
try adminMsg = NSDictionary(objects: [rawMsg["msg"]!["Title"]!!,
rawMsg["msg"]!["Text"]!!,
rawMsg["msg"]!["Time"]!!],
forKeys: ["Title", "Text", "Time"])
} catch {
adminMsg = nil
}
But I get this warning:
"no calls to throwing function occur within 'try' expression"
Does this mean I have no choice but to crash if an item is missing from the dictionary? I can't trap it and let my code gracefully tell the sender they sent me an invalid NSDictionary (unless I check it all out item-by-item in code)?
I can't trap it and let my code gracefully tell the sender they sent me an invalid NSDictionary
You cannot trap a fatal error, no. What you can do is catch the missing elements by using optional binding.
guard let title = message["Title"],
let text = message["Text"],
let time = message["Time"]
else {
// Missing data; inform caller
}
This is how you gracefully tell the client that there's something wrong with the data. You could return nil, an empty dictionary, or throw an error: whichever suits you best.
To make this properly Swifty, you should first define your data:
typealias RawMessage = Dictionary<String, [String : AnyObject]>
/** Important keys in a `RawMessage` */
enum RawMessageKey : String
{
case message = "msg"
case title = "Title"
case text = "Text"
case time = "Time"
}
enum RawMessageError : ErrorType
{
/** The `RawMessage` has no "message" key */
case NoMessage
/** The `RawMessage` is missing an expected inner key */
case MissingKey
}
Then your extraction function. This uses optional binding, not force unwrapping, to check that the key "msg" is present. Force unwrapping failure cannot be "caught"; that's not what it's for.
If the key is not present, you signal that to the caller by throwing your own error. Then use further optional binding to get the rest of the items. If any are missing, again throw an error.
/** Pull important values from `RawMessage` and repackage as `NSDictionary` */
func extractAdminMessage(rawMsg: RawMessage) throws -> NSDictionary
{
guard let message = rawMsg[String(RawMessageKey.message)] else {
throw RawMessageError.NoMessage
}
let keys = [String(RawMessageKey.title),
String(RawMessageKey.text),
String(RawMessageKey.time)]
guard let title = message[String(RawMessageKey.title)],
let text = message[String(RawMessageKey.text)],
let time = message[String(RawMessageKey.time)]
else {
throw RawMessageError.MissingKey
}
return NSDictionary(objects: [title, text, time],
forKeys: keys)
}
If you prefer, using flatMap could be an alternative to the stacked unbind:
let keys = [String(RawMessageKey.title),
String(RawMessageKey.text),
String(RawMessageKey.time)]
let objects = keys.flatMap { message[$0] }
guard objects.count == keys.count else {
throw RawMessageError.MissingKey
}
Here, any missing value will be dropped by flatMap; then if there aren't the same number of keys as objects, signal your caller.
I came up with this:
let rawMsg : NSDictionary =
["msg":["Title":"sometitle", "Text":"sometext", "Time":"sometime"]]
let adminMsg = NSMutableDictionary()
if let msg = rawMsg["msg"] as? NSDictionary {
for key in ["Title", "Text", "Time"] {
adminMsg[key] = msg[key]
}
}
That works even if a key is missing from the "msg" dictionary. You won't crash at any point during that, not matter how malformed rawMsg may be.
(It would be better if adminMsg were a Swift dictionary rather than a Cocoa NSDictionary, but at least this seems to cover the original problem domain.)
A few suggestions
Stop using NSDictionary and use Swift dictionary instead
Don't use exceptions to manage control flow
The force unwrap ! produce a fatal error, you cannot catch it
What you should do instead
if let
msg = rawMsg["msg"],
title = msg["Title"],
text = msg["Text"],
time = msg["Time"] {
let adminMsg:[String:Any] = ["Title": title, "Text": text, "Time": time]
}

How do you store a dictionary on Parse using swift?

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.