If you mean exiting out of your app to the home screen of the device, that’s called “crashing” and will likely get you rejected by App Review. On iOS you should never have a user-facing feature for quitting the app.
Have you considered using Bluetooth LE? The ESP32 includes BLE and it may be a better fit for your use case. Note there’s an existing BT SIG specification for a service called Automation IO that basically exposes GPIO over BLE, which sounds like what you are doing. You may even find an ESP32 library somewhere that implements that service. Or your own minimal service would work too of course.
Why do you enter a default text and not a placeholder ?
Placeholder isn’t the right choice for proposing text that the user may choose to accept without further editing; it should be used as a hint to help the user know what to enter. For example, if saving a new document the default (proposed) name could be Untitled and the placeholder could be Enter a name. The placeholder would appear only if the user erases the proposed name.
And here’s a tip on enabling the button to match the text. If you reuse the text change handler for .editingDidBegin like this:
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange), for: .editingDidBegin)
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange), for: .editingChanged)
...then it will get called just before the alert appears, but after the needed alert?.actions array is set up. So now your enable/disable logic can be in just one place.
The error is subtle. But toss this line in somewhere such as viewDidLoad and the issue will reveal itself:
assert(uploadSession.delegate! is URLSessionDataDelegate)
For people following along: he implemented some URLSessionTaskDelegate and URLSessionDataDelegate methods in the view controller, but the URL session knew only that it claimed to be a URLSessionDelegate. The fix was to change this:
extension UploadInv: UIDocumentPickerDelegate, URLSessionDelegate {
...to this:
extension UploadInv: UIDocumentPickerDelegate, URLSessionDataDelegate {
the app will forcibly go to the home screen
Nope, that’s not a supported app lifecycle in iOS.
I want the app to run in the background
First, please see this excellent summary of issues you need to consider: iOS Background Execution Limits.
After reviewing that information, does your desired behavior align with any of the supported scenarios?
Well urlSession(_:task:didCompleteWithError:) always gets called last because it indicates the end of the task.
And urlSession(_:dataTask:didReceive:completionHandler:) (response headers) needs to get called before urlSession(_:dataTask:didReceive:) (body) because depending on how you call the completion handler, it can change how the body gets handled subsequently. And it’s just natural that your code would receive the headers before the body.
But for your situation, I think you can simplify by eliminating the response method entirely. Note the documentation says it’s intended for specific cases that don’t seem to apply here. Instead, just check the error and HTTP response code in the completion method, and then process the accumulated body data as needed.
(Or if you keep the response method, note you need to call the completion handler to tell the system what to do next. The documentation isn’t clear on whether you should call it synchronously or asynchronously. Seems to me you should call it synchronously since it seems to work like a function return value, but I may be missing something subtle there.)
Also, a couple minor adjustments:
if let httpResponse = response as? HTTPURLResponse {
It’s generally safe to force the response as! HTTPURLResponse. If that optional binding were to actually fail, what then?
extension UploadInv: UIDocumentPickerDelegate, URLSessionDataDelegate, URLSessionTaskDelegate, URLSessionDelegate
You don’t need to list URLSessionDelegate or URLSessionTaskDelegate there, because URLSessionDataDelegate already extends them. I’m a little surprised there isn’t a compiler warning for that.
And if you solve the problem on your own, please give at least a short summary of the solution. Someone else with the same issue may benefit. Don’t just say “Never mind, I figured it out.”
Add an enum CodingKeys to your struct to declare exactly which properties are to be decoded. Simplified example:
struct Reminder: Identifiable, Decodable {
let id = UUID().uuidString
let title: String
enum CodingKeys: CodingKey {
case title // note that id is not listed here
}
}
See Encoding and Decoding Custom Types for more. If you also want to later encode the structure but include the id property, then you need to customize init(from:) and/or encode(to:) as described in the Encode and Decode Manually section.
Is your App type an actor, rather than struct or class? In @eskimo’s code above, changing struct App to actor App produces the error message you reported.
If you mean exiting out of your app to the home screen of the device, that’s called “crashing” and will likely get you rejected by App Review. On iOS you should never have a user-facing feature for quitting the app.
Have you considered using Bluetooth LE? The ESP32 includes BLE and it may be a better fit for your use case. Note there’s an existing BT SIG specification for a service called Automation IO that basically exposes GPIO over BLE, which sounds like what you are doing. You may even find an ESP32 library somewhere that implements that service. Or your own minimal service would work too of course.
Why do you enter a default text and not a placeholder ?
Placeholder isn’t the right choice for proposing text that the user may choose to accept without further editing; it should be used as a hint to help the user know what to enter. For example, if saving a new document the default (proposed) name could be Untitled and the placeholder could be Enter a name. The placeholder would appear only if the user erases the proposed name.
And here’s a tip on enabling the button to match the text. If you reuse the text change handler for .editingDidBegin like this:
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange), for: .editingDidBegin)
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange), for: .editingChanged)
...then it will get called just before the alert appears, but after the needed alert?.actions array is set up. So now your enable/disable logic can be in just one place.
The error is subtle. But toss this line in somewhere such as viewDidLoad and the issue will reveal itself:
assert(uploadSession.delegate! is URLSessionDataDelegate)
For people following along: he implemented some URLSessionTaskDelegate and URLSessionDataDelegate methods in the view controller, but the URL session knew only that it claimed to be a URLSessionDelegate. The fix was to change this:
extension UploadInv: UIDocumentPickerDelegate, URLSessionDelegate {
...to this:
extension UploadInv: UIDocumentPickerDelegate, URLSessionDataDelegate {
the app will forcibly go to the home screen
Nope, that’s not a supported app lifecycle in iOS.
I want the app to run in the background
First, please see this excellent summary of issues you need to consider: iOS Background Execution Limits.
After reviewing that information, does your desired behavior align with any of the supported scenarios?
Well urlSession(_:task:didCompleteWithError:) always gets called last because it indicates the end of the task.
And urlSession(_:dataTask:didReceive:completionHandler:) (response headers) needs to get called before urlSession(_:dataTask:didReceive:) (body) because depending on how you call the completion handler, it can change how the body gets handled subsequently. And it’s just natural that your code would receive the headers before the body.
But for your situation, I think you can simplify by eliminating the response method entirely. Note the documentation says it’s intended for specific cases that don’t seem to apply here. Instead, just check the error and HTTP response code in the completion method, and then process the accumulated body data as needed.
(Or if you keep the response method, note you need to call the completion handler to tell the system what to do next. The documentation isn’t clear on whether you should call it synchronously or asynchronously. Seems to me you should call it synchronously since it seems to work like a function return value, but I may be missing something subtle there.)
Also, a couple minor adjustments:
if let httpResponse = response as? HTTPURLResponse {
It’s generally safe to force the response as! HTTPURLResponse. If that optional binding were to actually fail, what then?
extension UploadInv: UIDocumentPickerDelegate, URLSessionDataDelegate, URLSessionTaskDelegate, URLSessionDelegate
You don’t need to list URLSessionDelegate or URLSessionTaskDelegate there, because URLSessionDataDelegate already extends them. I’m a little surprised there isn’t a compiler warning for that.
And if you solve the problem on your own, please give at least a short summary of the solution. Someone else with the same issue may benefit. Don’t just say “Never mind, I figured it out.”
Add an enum CodingKeys to your struct to declare exactly which properties are to be decoded. Simplified example:
struct Reminder: Identifiable, Decodable {
let id = UUID().uuidString
let title: String
enum CodingKeys: CodingKey {
case title // note that id is not listed here
}
}
See Encoding and Decoding Custom Types for more. If you also want to later encode the structure but include the id property, then you need to customize init(from:) and/or encode(to:) as described in the Encode and Decode Manually section.
Is your App type an actor, rather than struct or class? In @eskimo’s code above, changing struct App to actor App produces the error message you reported.