Pass an empty URL as parameter in Swift - swift

I have this array of tuplets containing strings and a url
var notifications:[(body: String, header: String, icon: URL)] = []
Now, I want to append a tuplet with an empty URL
I tried
notifications.append((body: "some text, header: "some more text", icon: nil))
but that is not allowed
What is the way to do this?

If you want to allow for icon to be nil, you need to make it optional.
var notifications:[(body: String, header: String, icon: URL?)] = []
But you really should make a struct instead of using a tuple. A struct is more powerful, gives you more options, and just better to work with.

icon has to be of type URL? (a.k.a. Optional) if you wish to be able to assign nil to it.

Related

Why does the behaviour of LocalizedStringKey depend on whether I pass a string interpolation to its initialiser?

While trying to answer this question, I found a strange behaviour.
Text(LocalizedStringKey("Hello \(Image(systemName: "globe"))"))
displays a globe, but
Text(LocalizedStringKey("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))")))
Text(LocalizedStringKey("Hello" + "\(Image(systemName: "globe"))"))
displays "Hello" followed by a jumble of SwiftUI's internal jargon mess.
An even more minimal example would be:
let x = "\(Image(systemName: "globe"))"
print(LocalizedStringKey.init(x))
print(LocalizedStringKey.init("\(Image(systemName: "globe"))"))
The values I'm passing to LocalizedStringKey.init should be the same, both "\(Image(systemName: "globe"))", but The first prints
LocalizedStringKey(key: "%#", hasFormatting: true, arguments: [...])
and the second prints
LocalizedStringKey(key: "Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $7ff91ccb3380).NamedImageProvider>)", hasFormatting: false, arguments: [])
It appears that LocalizedStringKey.init changes its behaviour depends on whether the arguments I pass is an (interpolated) string literal or not.
As far as I can see, the two calls to LocalizedStringKey.init are calling the same initialiser. There is only one parameter-label-less initialiser in LocalizedStringKey, which takes a String.
If there were also an initialiser that takes a LocalizedStringKey, the results would be much more understandable. LocalizedStringKey has custom string interpolation rules, and one specifically for Image, after all. But this is the only initialiser with no parameter labels as far as I know.
It would also be somewhat understandable if the initialiser's parameter is #autoclosure () -> String. If the expression I pass in is lazily evaluated, the method might be able to "peek inside" the closure by some means unknown to me. But the parameter isn't an auto closure.
What seems to be happening here is that the compiler is creating a LocalizedStringKey with key being that same pattern as the interpolation you passed in, even though the parameter is a String!
What is actually going on here? Did I miss a hidden initialiser somewhere?
TL;DR: the behaviour you're seeing comes from ExpressibleByStringInterpolation. But read on for more fun!
LocalizedStringKey becomes easier to understand if you think of it purely as a convenience to allow SwiftUI interface elements to be localizable "for free" when using string literals. There's only one real time you'd use it directly.
Consider Text. There are two relevant initializers:
init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)
which will attempt to localize the text passed in, and
init<S>(_ content: S) where S : StringProtocol
which will display the string without altering it.
If you call Text("Hello"), which initializer is used?
String literals conform to StringProtocol, but LocalizedStringKey is also ExpressibleByStringLiteral. The compiler would not know which one to choose.
To get "free" localization, the StringProtocol initializer is marked with #_disfavoredOverload, which tells the compiler to assume that the string literal is a LocalizableStringKey rather than a String.
Therefore, Text("Hello") and Text(LocalizedStringKey("Hello")) are equivalent.
let string = "Hello"
Text(string)
In this case, there is no conflict - the compiler uses the StringProtocol initializer and the string is not localized.
What does this have to do with your question? LocalizedStringKey is also ExpressibleByStringInterpolation, which is where your "hidden initializer" comes from. But like the examples above, this only comes into play if you are initializing it with a single, interpolated string.
Text("Hello \(Image(systemName: "globe"))")
You're passing an interpolated string, so the compiler can deal with it and add the image into the interpolation.
Text("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))"))
Here, replacingOccurrences(of: is evaluated first, meaning your argument is a String, which is not treated as a LocalizedStringKey expressed-via-string-interpolation. You're essentially seeing the description of the image.
A similar thing happens with the example with + in it. That implicitly makes a String, so you lose the special image interpolation that LocalizedStringKey gives you.
For your last code example:
let x = "\(Image(systemName: "globe"))"
print(LocalizedStringKey.init(x))
print(LocalizedStringKey.init("\(Image(systemName: "globe"))"))
x is a string containing a description of the image. Remember, only LocalizedStringKey has the magic power to actually understand and represent Image. Any other string interpolation will fall back to the description of the interpolated object.
The first initializer is passing a string (which is treated as a key, that's the only time you'd really directly use LocalizedStringKey, if you were generating keys at run time and wanted to use them for lookup).
The second initializer is using ExpressibleByStringInterpolation and is using LocalizedStringKey.StringInterpolation to insert images into its internal storage, which can then get rendered by Text.

Swift cannot create array of tuples containing a nested type

I'm trying to do something that should be possible, but I'm getting a strange error. I have a nested type Gravl.Node and am trying to instantiate an array of tuples like so:
var attributes = [(attribute: String?, value: Gravl.Node)]()
…but Swift is complaining:
Cannot call value of non-function type '[(attribute: String?.Type, value: Gravl.Node.Type)]'
Any idea what gives? If I replace the nested type with something else it works fine.
Is this a bug in the parser/compiler?
Yes, this is a bug as noted by this question.
Following the example of that Q & A, you can change the syntax of the way you create the array:
var attributes: [(attribute: String?, value: Gravl.Node)] = []
or
var attributes = Array<(attribute: String?, value: Gravl.Node)>()
In this case, you can also work around the issue by creating a typealias:
Example:
class Gravl {
enum Node {
case first, last
}
}
typealias Gravl_Node = Gravl.Node // work around Swift bug
var attributes = [(attribute: String?, value: Gravl_Node)]()
attributes.append((attribute: "hello", value: Gravl.Node.first))

Principles behind when to use an optional type in Swift?

When designing classes/structs in Swift, what are the best practices for identifying when you should use an Optional type?
For example, lets say I'm mapping a JSON response to a Photo object and there is a property photographerName which is just a string. Sometimes we receive empty strings for the field photographerName by the response.
Should my Photo object use a String? type and assign it to nil? Or use a String type and set it to an empty string?
It really depends on the application architecture and data structures that you going to use in the application.
There is no rule that will describe how properly use optional chaining in the application architecture.
At first, we should know what is optional chaining.
Optional chaining is a process for querying and calling properties,
methods, and subscripts on an optional that might currently be nil. If
the optional contains a value, the property, method, or subscript call
succeeds; if the optional is nil, the property, method, or subscript
call returns nil. Multiple queries can be chained together, and the
entire chain fails gracefully if any link in the chain is nil.
Based on my experience, I am using optional values in my data structure when I clearly could not define if my model could or could not have some relationship or property.
Example:
struct User {
let name: String
let email: String
var friends: [String]?
init(name: String, email: String) {
self.name = name
self.email = email
}
}
let user = User(name: "Oleg", email: "Oleg#email.com")
I know that user will have name and email but I do not know if he would have any friends. Also, in this way, I could prevent my code from execution of operation on nil objects.
In case that you describe it really depends on the architecture of the application and requirements. For example, should we show placeholder if photographerName is empty or not? Are any additional operation that are using photographerName property?
It´s up to you what you like to use, but if you receive a response with a property that can be nil then you should use optional and handle it in the code.
Something like this:
struct Photo {
let photographerName: String?
init(photographerName: String? = nil) {
self.photographerName = photographerName
}
}
And then in your code to check if photographerName has a value:
if let photographerName = photo.photographerName {
// use photographerName in here
}

Type of expression is ambiguous without more context Swift

I am getting a 'Type of expression is ambiguous without more context ' on this part of code from a project I am trying to upgrade to latest Swift version. I can't seem to figure it out. I tried different things but can't get it to work.
The problem is on the syntax of this line
let imageToDeleteParameters = imagesToDelete.map { ["id": $0.id, "url": $0.url.absoluteString, "_destroy": true] }
Full code:
extension TutorialCreationRequest: WebserviceParametrable {
func toParameters() -> [String: AnyObject] {
let imageParameters = images.map { ["url": $0] }
let imageToDeleteParameters = imagesToDelete.map { ["id": $0.id, "url": $0.url.absoluteString, "_destroy": true] }
return [
"title": title,
"is_draft": isDraft,
"difficulty": difficulty,
"duration": duration,
"cost": cost,
"user_id": userId,
"description": description,
"to_sell": toSell,
"images": [imageParameters, imageToDeleteParameters].flatMap { $0 }
]
}
}
This happens when you have a function with wrong argument names.
Example:
functionWithArguments(argumentNameWrong: , argumentName2: )
and You declared your function as:
functionWithArguments(argumentName1: , argumentName2: ){}
This usually happens when you changed the name of a Variable. Make sure you refactor when you do that.
This can happen if any part of your highlighted method or property is attempting to access a property or method with the incorrect type.
Here is a troubleshooting checklist, make sure:
the type of arguments match in the call site and implementation.
the argument names match in the call site and implementation.
the method name matches in the call site and implementation.
the returned value of a property or method matches in the usage and implementation (ie: enumerated())
you don't have a duplicated method with potentially ambiguous types such as with protocols or generics.
the compiler can infer the correct type when using type inference.
A Strategy
Try breaking apart your method into a greater number of simpler method/implementations.
For example, lets say you are running compactMap on an array of custom Types. In the closure you are passing to the compactMap method, you initialize and return another custom struct. When you get this error, it is difficult to tell which part of your code is offending.
For debugging purposes, you can use a for in loop instead of compactMap.
instead of passing the arguments, directly, you can assign them to constants in the for loop.
By this point, you may come to a realization, such as, instead of the property you thought you wanted to assign actually had a property on it that had the actual value you wanted to pass.
Not an answer to this question, but as I came here looking for the error others might find this also useful:
For me, I got this Swift error when I tried to use the for (index, object) loop on an array without adding the .enumerated() part ...
The compiler can't figure out what type to make the Dictionary, because it's not homogenous. You have values of different types. The only way to get around this is to make it a [String: Any], which will make everything clunky as all hell.
return [
"title": title,
"is_draft": isDraft,
"difficulty": difficulty,
"duration": duration,
"cost": cost,
"user_id": userId,
"description": description,
"to_sell": toSell,
"images": [imageParameters, imageToDeleteParameters].flatMap { $0 }
] as [String: Any]
This is a job for a struct. It'll vastly simplify working with this data structure.
I had this message when the type of a function parameter didn't fit. In my case it was a String instead of an URL.
Explicitly declaring the inputs for that mapping function should do the trick:
let imageToDeleteParameters = imagesToDelete.map {
(whatever : WhateverClass) -> Dictionary<String, Any> in
["id": whatever.id, "url": whatever.url.absoluteString, "_destroy": true]
}
Substitute the real class of "$0" for "WhateverClass" in that code snippet, and it should work.
I got this error when I put a space before a comma in the parameters when calling a function.
eg, I used:
myfunction(parameter1: parameter1 , parameter2: parameter2)
Whereas it should have been:
myfunction(parameter1: parameter1, parameter2: parameter2)
Deleting the space got rid of the error message
In my case, this error message shown when I don't added optional property to constructor.
struct Event: Identifiable, Codable {
var id: String
var summary: String
var description: String?
// also has other props...
init(id: String, summary: String, description: String?){
self.id = id
self.summary = summary
self.description = description
}
}
// skip pass description
// It show message "Type of expression is ambiguous without more context"
Event(
id: "1",
summary: "summary",
)
// pass description explicity pass nil to description
Event(
id: "1",
summary: "summary",
description: nil
)
but it looks always not occured.
I test in my playground this code, it show warning about more concrete
var str = "Hello, playground"
struct User {
var id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
User(id: "hoge") // Missing argument for parameter 'name' in call
For me the case was Type inference
I have changed the function parameters from int To float but did not update the calling code, and the compiler did not warn me on wrong type passed to the function
Before
func myFunc(param:Int, parma2:Int) {}
After
func myFunc(param:Float, parma2:Float) {}
Calling code with error
var param1:Int16 = 1
var param2:Int16 = 2
myFunc(param:param1, parma2:param2)// error here: Type of expression is ambiguous without more context
To fix:
var param1:Float = 1.0f
var param2:Float = 2.0f
myFunc(param:param1, parma2:param2)// ok!
My problem were the parameters without default value
I changed
let contaPadrao = RedeConta(
agencia: cPadrao?.agencia,
conta: cPadrao?.conta,
dac: cPadrao?.dac
)
to
let contaPadrao = RedeConta(
agencia: cPadrao?.agencia ?? "",
conta: cPadrao?.conta ?? "",
dac: cPadrao?.dac ?? ""
)
You have two " " before the =
let imageToDeleteParameters = imagesToDelete.map { ["id": $0.id, "url": $0.url.absoluteString, "_destroy": true] }
In my case it happened with NSFetchedResultsController and the reason was that I defined the NSFetchedResultsController for a different model than I created the request for the initialization (RemotePlaylist vs. Playlist):
var fetchedPlaylistsController:NSFetchedResultsController<RemotePlaylist>!
but initiated it with a request for another Playlist:
let request = Playlist.createFetchRequest()
fetchedPlaylistsController = NSFetchedResultsController(fetchRequest: request, ...
In my case, I ran into this error when I was creating a distribution build, and a class was referring to Debug only context method.
Something like this. Try compiling the below class for the Distribution build.
class MyClass {
func sayHello() {
helloWorld()
}
#if DEBUG
func helloWorld() {
print("Hello world")
}
#endif
}
Make sure if there is any delegate methods are available with extension, then implement those and error will disappear.
In my case, the arguments I was passing had optional String values. Providing a default value to fall back on ( in case the value is nil ) solved this issue for me.
I changed this -
router?.pushToDetailsScreen(
gitHubRepositoryOwnerName: gitHubRepositoryDetails?[index].owner?.login,
gitHubRepositoryName: gitHubRepositoryDetails?[index].name,
avatarUrl: gitHubRepositoryDetails?[index].owner?.avatar_url)
to this -
router?.pushToDetailsScreen(
gitHubRepositoryOwnerName: gitHubRepositoryDetails?[index].owner?.login ?? "",
gitHubRepositoryName: gitHubRepositoryDetails?[index].name ?? "",
avatarUrl: gitHubRepositoryDetails?[index].owner?.avatar_url ?? "")
This error could be shown due to multiple reasons. One of the most prominent reasons is a type mismatch. For example,
Suppose, parameter icons is a type of array and we passed an Enum as IconDismiss.
## Wrong
text.config = TextField.Config(isClearable: true, icons: IconDismiss)
## Correct
text.config = TextField.Config(isClearable: true, icons: [IconDismiss])
In my case it was very similar to what #scottyBlades said. I had changed a member variable name and not updated it in a callback and that caused the confusing/unhelpful error.
class SomeClass {
var newVarName: String
func doSomething() {
Async.makeCall { result in // << error shows here
self.oldVarName = result // not changing it to newVarName was the cause
}
}
}
As theEye's answer it is not an answer to this question, but as I also came here looking for the error im posting my case as others might find this also useful:
I got this error message when I was by error trying to calculate a value of two different types.
In my case I was trying to divide a CGFloat by a Double

Cannot invoke 'append' with an argument list of type'(String)'

What is wrong here and how to solve this problem?
struct Venue {
let building: String
var rooms: [String]?
}
func addRoom(building: String, room: String) {
if let venueIndex = find(venues.map {$0.building}, building) {
venues[venueIndex].rooms.append(room) //Cannot invoke 'append' with an argument list of type'(String)'
}
}
var venues: [Venue] = [...]
The problem is that venues[venueIndex].rooms is not a [String] but a [String]?. Optionals don’t have an append method – the value wrapped inside them might, but they don’t.
You could use optional chaining to do the append in the case where it isn’t nil:
venues[venueIndex].rooms?.append(room)
But you may instead want to initialize rooms to an empty index instead when it is nil, in which case you need to do a slightly messier assignment rather than an append:
venues[venueIndex].rooms = (venues[venueIndex].rooms ?? []) + [room]
However, it is worth asking yourself, does rooms really need to be optional? Or could it just be a non-optional array with a starting value of empty? If so, this will likely simplify much of your code.