SwiftUI - Why can't I use an Alert within an if statement - if-statement

I'm currently working through the #100daysofswiftUI and have a quick question regarding the use of alerts within if statements. This particular task is at the end of the second project.
Here is the code:
.alert(isPresented: $showingScore) {
if x == 1 {
Alert(title: Text(scoreTitle), message: Text("The correct answer was \(countries[correctAnswer])"), dismissButton: .default(Text("Restart")) {
self.askQuestion()
})
}
}
I feel like this code should work however I have a yellow alert over the Alert line saying:
Result of 'Alert' initializer is unused
I don't know what this means, how can I fix this?

You need to return an Alert in the .alert modifier, not an if statement.
For this you can create a computed property which returns an Alert:
var alert: Alert {
if x == 1 {
return Alert(
title: Text(scoreTitle),
message: Text("The correct answer was \(countries[correctAnswer])"),
dismissButton: .default(Text("Restart")) {
self.askQuestion()
}
)
} else {
return Alert(...) // return some other `Alert`
}
}
and use it in the .alert modifier:
.alert(isPresented: $showingScore) {
alert
}
Just make sure you only enable showingScore when you want to show an alert.

you should put your if condition with showingScore,
do something like this
if x == 1 {
showingScore = true
}
your alert will look like this
.alert(isPresented: $showingScore) {
Alert(title: Text(scoreTitle), message: Text("The correct answer was \(countries[correctAnswer])"), dismissButton: .default(Text("Restart")) {
self.askQuestion()
})
}

Related

Purchase a product using it's id or displayName

I've been following a few tutorials online regarding setting up Storekit in my app. I've gotten as far as successfully requesting all the products and holding them in a products array. The next step of these tutorials is almost always listing out the products using a ForEach like so:
ForEach(products) { product in
Button {
Task.init {
try await purchaseProduct(product)
}
} label: {
HStack {
Text(product.displayName)
Spacer()
Text(product.displayPrice)
}
}
}
This doesn't work for my use case, unfortunately. The design I'm working off has 3 buttons in different parts of the screen, each of which initiate a purchase request for a different product.
I've managed to get some of the way there by doing this:
Button {
Task.init {
try await purchaseProduct(products.first!)
}
} label: {
HStack {
Text("\(products.first?.displayName ?? "No name")")
Spacer()
Text("\(products.first?.displayPrice ?? "No price")")
}
}
But this feels really hacky to me, for the following reasons:
Force unwrapping doesn't feel correct
I can make this work for the .first and .last item in the products array but I don't know how to get the second item, and this also means if the order of items inside products changes, my UI ties the wrong product to their respective button.
Here's my purchaseProduct function:
func purchaseProduct(_ product: Product) async throws -> StoreKit.Transaction {
let result = try await product.purchase()
switch result {
case .pending:
throw PurchaseError.pending
case .success(let verification):
switch verification {
case .verified(let transaction):
await transaction.finish()
return transaction
case .unverified:
throw PurchaseError.failed
}
case .userCancelled:
throw PurchaseError.cancelled
#unknown default:
assertionFailure("Unexpected result")
throw PurchaseError.failed
}
}
Ideally, I'm looking to do something like this:
if let productOne = products.PRODUCTID {
Button {
Task.init {
try await purchaseProduct(productOne)
}
} label: {
HStack {
Text("\(productOne.displayName)")
Spacer()
Text("\(productOne.displayPrice)")
}
}
}
But I'm struggling to wrap my head around how to get there.
In order to achieve your desired if let productOne = products.PRODUCTID, you can use first(where:): https://developer.apple.com/documentation/swift/array/first(where:)
if let productOne = products.first(where: {$0.id == "iap.myapp.ProductOne"}) {
// ...
}

SwiftUI: Difference between async{} and Task.init{ }

I am using Xcode 13.4 and trying to launch an async function when the Picker changes.
Picker(NSLocalizedString("Please choose a project", comment: ""), selection: $selectedProjectKey) {
ForEach(projectKeys, id: \.self) {
Text($0)
}
}
.font(.system(size: 14))
.onChange(of: selectedProjectKey, perform: { (selectedKey) in
print("selected key \(selectedKey)")
async {
do {
let projectTasks = try await api.getProjectTasksByProjectKey(projectKey: selectedKey)
projectTasksKeys = projectTasks.map{$0.key}
} catch {
/// To define error behavour
}
}
})
It works, but I know that async is deprecated (yellow warning).
That's why, I tried with Task.init {... } or Task { ... } and instead, I get an error:
"Trailing closure passed to parameter of type 'Decoder' that does not
accept a closure".
I suppose that I did something definitely wrong but I can't understand what it is and what difference there is between async{...} and Task.init { ... } .

iOS 15 NavigationLink pop automatically if destination is created dynamically

This is a problem on iOS 15 RC only. It works all good on iOS 14.
The problem is if my destination on NavigationLink is created dynamically, it pop automatically if i try push a view via NavigationLink. Here is a bit code snippet
NavigationLink(
destination: (createSettingNavigationLink(name: nameAndImage[0])),
label: {
CellButton(title: nameAndImage[0], image: nameAndImage[1])
}
func createSettingNavigationLink(name: String) -> some View {
if name == "shop.Settings" {
return ShopSettings()
}
if name == "shop.Goods" {
return ShopGoodsManagementPage()
}
if name == "shop.Order" {
return ShopOrderPage()
}
if name == "Customer Service" {
return ServiceListPage(isStore: true)
}
if name == "shop.Performance" {
return PerformanceManagementPage()
}
if name == "shop.Earnings" {
return CommissionSummaryPage()
}
if name == "shop.Increase_sales" {
return BCWebView(urlStr: Constants.increaseSalesGuide)
.navigationBarTitle(Text("Increase Sales Guide"), displayMode: .inline)
.navigationBarHidden(false)
)
}
return EmptyView()
}
)
By the way, the approach mentioned here SwiftUI Unexpectedly NavigationLink pops automatically does not help.
My final solution is to change the above function to struct view. I think it makes sense since the struct view seems to keep states while function is pure stateless.

SwiftUI Runtime Error from if condition { Text() } in watchOS

As far as I read about conditional Views this code should work. But it doesn't.
struct Consts {
static let myCondition = false //no difference if true or false
}
struct ContentView: View {
#State var toggle: Bool = false
var body: some View {
List() {
Text("Short Text is in first line")
Text("Second Line Text is a little longer but not much")
if Consts.myCondition {
Text("This is a conditional text. As in: when the user hasn't purchased the option he / she don't need a hint how to use this feature.")
// } else {
// Text("else doesn't help either.")
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
Text("Here we have a longer Text. Dont know what to type any more.")
Text("More text which is longer than a few lines.")
Text("Not so long Text")
}
.navigationTitle("Hints & Settings")
}
}
It compiles without warnings or errors. It loads up and displays fine, on simulator and on device. But every time I scroll the List upwards from the end, as soon as this if condition { Text() } should become visible the app crashes with
Fatal error: file SwiftUI, line 0
2021-03-07 06:36:26.548126+0100 WTFif WatchKit Extension[23163:641812] Fatal error: file SwiftUI, line 0
This is not limited to watchOS. It reproduces the same way in iOS, just the Texts have to be longer so that the if condition { Text() } becomes invisible when scrolling.
I have worked around the error with an array, conditional ranges and two ForEach() blocks.
struct ContentView: View {
let myHints = ["Short Text is in first line",
"Second Line Text is a little longer but not much",
"This is a conditional text. As in: when the user hasn't purchased the option he / she don't need to hint how to use this feature.",
"Here we have a longer Text. Dont know what to type any more.",
"More text which is longer than a few lines.",
"Not so long Text"]
var myUpperRange: ClosedRange<Int> {
if Consts.myCondition {
return 0...1
} else {
return 0...2
}
}
var myLowerRange: ClosedRange<Int> {
return 3...5
}
#State var toggle: Bool = false
var body: some View {
List() {
ForEach (myUpperRange, id: \.self) { i in
Text(myHints[i])
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
ForEach (myLowerRange, id: \.self) { i in
Text(myHints[i])
}
}
.navigationTitle("Hints & Settings")
}
}
My question basically is: am I not getting it or did I find a bug in Xcode / SwiftUI? Should my code above work? What could I have done different to make it work with the simple list of Texts?
Background: I also have a TabView with an if condition { MyTab() } which works without crashing. Do I have to worry that this might crash in the same way? Should I work around this as well before shipping?
PS: I am using Xcode 12.4 (12D4e)
Apparently this has been fixed by iOS 15 Beta 4: on my test device I had the error prior to the Beta 4 update, using a test app compiled with Xcode 13 Beta 3. After the update to iOS 15 Beta 4 the error is gone.
So I think it’s reasonable to say that the update to iOS 15 Beta 4 did fix the error.

Type of expression is ambiguous without more context making a personal app

This is my code when I add TextField($order.name, placeholder: Text("Name")).
I get a error saying "Type of expression is ambiguous without more context" on the line where I have Text("Number of Cakes: \(order.quantity)").
My code :
Stepper (value: $order.quantity, in: 3...20) {
Text("Number of Cakes: \(order.quantity)")
}
}
Section {
Toggle(isOn: $order.specialRequestsEnabled){
Text("Any special requests?")
}
if order.specialRequestsEnabled {
Toggle(isOn: $order.extraFrosting) {
Text("Add extra frosting")
}
Toggle(isOn: $order.addSprinkeles) {
Text("Add extra sprinkles")
}
}
}
Section {
TextField($order.name, placeholder: Text("Name"))
}
SwiftUI can, at the moment, often show errors that are quite a way off from the error site. These errors can show up both before and after the actual error and be completely unrelated to the real error. Best is to comment out all unnecessary code, or even create a simple example and get that working first.
There is no such constructor in TextField, use instead
Section {
TextField("Name", text: $order.name)
}