I was reviewing "old" code (not that old, but a developer went away and we are documenting and reviewing his code), when, in the context of a iOS Share Extension, I found the following two lines:
let content = self.extensionContext!.inputItems[0] as! NSExtensionItem
for attachment in content.attachments as! [NSItemProvider] {
The first line: I red the docs and found inputItems can be empty too, so I suppose that forced cast will crash the app if this thing should happen (I don't know exactly how, but maybe it could).
The second line: same as above, with the difference that if you have no crash in the first line you probably won't have another here.
Question 1: is it a good idea to check the length of inputItems before the loop?
Question 2: I made a little edit to this code and I changed the first line to this:
let content = self.extensionContext!.inputItems[0] as? NSExtensionItem
After doing so, XCode suggests a correction to the second line I don't like very much (I consider it not readable):
for attachment in (content?.attachments as? [NSItemProvider])!
is the XCode suggestion the way to go?
Any comment is appreciated. Thanks!
It's always a good idea to unwrap optionals before accessing the object itself.
You can use guard to unwrap the optional chain before proceeding to work with the content.
guard let content = self.extensionContext?.inputItems.first as? NSExtensionItem else { return }
guard let attachments = content.attachments as? [NSItemProvider] else { return }
for attachment in attachments {
// Do stuff
}
Resources:
Statements
Patterns
You might want to review the documentation for Swift optionals
Forced unwrapping will crash the app when encountering nil values which are different from empty arrays.
Question 1: No, you don't have to, for-in loops account for the length of the array. Also, the code will loop over content.attachments not inputItems.
Question 2: Your edit of the first line caused content to become an optional value, requiring some kind of unwrapping in the second line
If you can be sure the casts will always work the way your developer had it is fine. If you want more safety I would probably do:
guard let content = self.extensionContext?.attachments.first as? NSExtensionItem,
let attachments = content.attachments as? [NSItemProvider] else
{
// fatalError()
return
}
for attachment in attachments
{
//
}
Related
I have some code in my app, some of which I found online in a tutorial somewhere. Everything seems to work okay, but I don't understand something. In the line that starts "let dataDescription = document...." I don't understand what exactly that is doing, since "dataDescription" is never subsequently used anywhere. I understand that "document.data().map..." is retrieving the data from the document, but then "dataDescription" is never used anywhere after that. I just want to understand what's happening. I do find a lot of code online to use, but I hate just using it without understanding what's going on.
let docRef = db.collection("jammers").document(userDocumentID)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
let data = document.data()
//Code for saving or deleting Sings field
if (data!["Sings"] != nil) //If Sings field exists
{
if self.singsSwitch.isOn //If the switch is set to on
{
//Update Sings field
docRef.updateData(["Sings": true])
}
I'm working on WatchKit App. In this app, there are some fields that the user should fill it,
I searched how to deal with input fields in iWatch, and I found the following code:
presentTextInputController(withSuggestions: ["1"], allowedInputMode: WKTextInputMode.plain) { (arr: [Any]?) in
if let answers = arr as? [String] {
if let answer = answers[0] as? String {
self.speechLabel.setText(answer)
}
}
}
and this code gives me two choices: Diction and scribble, i.e
In my App, I want to support only the scribble not both of them,
I tried to pass withSuggestions parameter as nil, but the app direct me to dictiation, not to scribble.
Is there a way to let the user only use scribble?
Alright, I am not familiar with structs or the ordeal I am dealing with in Swift, but what I need to do is create an iMessage in my iMessage app extension with a sticker in it, meaning the image part of the iMessage is set to the sticker.
I have pored over Apple's docs and https://www.captechconsulting.com/blogs/ios-10-imessages-sdk-creating-an-imessages-extension but I do not understand how to do this or really how structs work. I read up on structs but that has not helped me accomplishing what Apple does in their sample code (downloadable at Apple)
What Apple does is they first compose a message, which I understood, taking their struct as a property, but I take sticker instead
guard let conversation = activeConversation else { fatalError("Expected a conversation") }
//Create a new message with the same session as any currently selected message.
let message = composeMessage(with: MSSticker, caption: "sup", session: conversation.selectedMessage?.session)
// Add the message to the conversation.
conversation.insert(message) { error in
if let error = error {
print(error)
}
}
They then do this (this is directly from sample code) to compose the message:
fileprivate func composeMessage(with iceCream: IceCream, caption: String, session: MSSession? = nil) -> MSMessage {
var components = URLComponents()
components.queryItems = iceCream.queryItems
let layout = MSMessageTemplateLayout()
layout.image = iceCream.renderSticker(opaque: true)
layout.caption = caption
let message = MSMessage(session: session ?? MSSession())
message.url = components.url!
message.layout = layout
return message
}
}
Basically this line is what Im having the problem with as I need to set my sticker as the image:
layout.image = iceCream.renderSticker(opaque: true)
Apple does a whole complicated function thing that I don't understand in renderSticker to pull the image part out of their stickers, and I have tried their way but I think this is better:
let img = UIImage(contentsOfURL: square.imageFileURL)
layout.image = ing
layout.image needs a UIImage, and I can get the imageFileURL from the sticker, I just cant get this into a UIImage. I get an error it does not match available overloads.
What can I do here? How can I insert the image from my sticker into a message? How can I get an image from its imageFileURL?
I'm not sure what exactly the question is, but I'll try to address as much as I can --
As rmaddy mentioned, if you want to create an image given a file location, simply use the UIImage constructor he specified.
As far as sending just a sticker (which you asked about in the comments on rmaddy's answer), you can insert just a sticker into an iMessage conversation. This functionality is available as part of an MSConversation. Here is a link to the documentation:
https://developer.apple.com/reference/messages/msconversation/1648187-insert
The active conversation can be accessed from your MSMessagesAppViewController.
There is no init(contentsOfURL:) initializer for UIImage. The closest one is init(contentsOfFile:).
To use that one with your file URL you can do:
let img = UIImage(contentsOfFile: square.imageFileURL.path)
I'm getting this error in latest version of xcode using swift 2
on line
let s = linkTxt.text
Text in linkTxt appears by button "pasteFromClipBoard"
let s = linkTxt.text
let u = NSURL(string: s!)
let file = u?.lastPathComponent
What is the reason of it and how to fix it?
Update:
the problem appears in function saveData() which calls when file downloading is finished. It calls from NSURLSessionDataTask function. More interesting, that in start-downloading-button there are the same lines where filename is generating and there is no such error on it. I fixed these issues by declaring variables, writing text's values into them and use these variables in saveData() except textObject.text; I had to delete lines with NSUserDefaults from saveData() too because I got the same error. Did understand nothing >_<
Update 2:
It's really a bug. I've deleted this line and wrote again - problem fixed
linkTxt.txt is returning nil and NSURL(string: s!) will try to forcefully unwrap it.
let s = linkTxt.text
if let s = linkTxt.txt {
let u = NSURL(string: s!)
let file = u?.lastPathComponent
}
I am relatively new to Swift and programming. I'm developing an app which heavily relies on information downloaded from the server. So in a lot of ViewControllers, I use NSURLSession and NSJSONSerialization to download the JSON into my app.
Every time I wanted to subscript the dictionary, for example timetableDict?["timetable"]["class"]["day"]["lesson"][0]["name"], something like Cannot subscript a value of type [String : AnyObject] with an index type of String shows up as an error.
I understand that I should avoid using AnyObject in my code, but the dictionary from the server is heavily nested with structures like this one:
"timetable": ["class": ({
day = ({
lesson = ({
name = (MATHEMATICS, ENGLISH),
classOrder = 0,
teacher = (Someone)
}),
({
name = FRENCH,
classOrder = 1,
teacher = (Someone)
)}
)}
)}]
The problem with this structure is that it is heavily nested and has different types when it gets to "name", "classOrder" and "teacher". It is very hard for me not to use AnyObject. However, this error has been annoying for me for a very long time. I would greatly appreciate it if someone could help me out on this. Thanks in advance!
I suggest taking a look at SwiftyJSON : https://github.com/SwiftyJSON/SwiftyJSON
It's a framework/library designed to handle JSON in a very much more elegant way than what's build into swift (especially for heavy nested structures like yours). It's easy to use and has an excellent tutorial.
EDIT: (Added sample code)
Example from the swiftyJSON tutorial :
let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
if let statusesArray = JSONObject as? [AnyObject],
let status = statusesArray[0] as? [String: AnyObject],
let user = status["user"] as? [String: AnyObject],
let username = user["name"] as? String {
// Finally we got the username
}
even with optional chaining quite messy :
let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
if let username = (((JSONObject as? [AnyObject])?[0] as? [String: AnyObject])?["user"] as? [String: AnyObject])?["name"] as? String {
// What a disaster
}
With swiftyJSON
let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string {
//Now you got your value
}
SwiftlyJSON that #Glenn mentions is not a bad system (though I find it over-reliant on string lookups, which is fragile). The deeper point, however, is that you want to validate and parse your JSON in one place, and turn it into a non-JSON Swift data structure for use by the rest of your program. SwiftlyJSON can be a decent tool for doing that, but I would use it to unload data into a an array of structs.
Working with JSON throughout your system is extremely error-prone and cumbersome, even wrapped up in SwiftlyJSON. If you unload the data once, then you can check for errors one time and everywhere else you know that the data is correct. If you pass around JSON structures, then you must check every single time and deal with possibly missing or incorrect data. Think through the case where the server sends you JSON in a format you didn't expect. How many times do you want to test for that?