I think it closer to success interactive widget in swiftui, but what wrong with button?

1 week ago 12
ARTICLE AD BOX

The notes: Last time, I posted, it was a simple designed the UI. However, this post has updated.

I studying the interactive widget tutorial in the swiftui. So, now I am tried to write my code to see if I successful, but so closer to success. Button is problems!

For who ask me to try AI:

I hate to use the AI to solve my coding, even Xcode's AI feature, too. It is because I prefer my brain to creative.

Here is full of the code:

import SwiftUI import SwiftUI import WidgetKit import AppIntents // MARK: - MODEL struct ListOfLanguage: Identifiable, Hashable { var id: String { hiraganaText } var headerTitle: String = "" var hiraganaText: String = "" var kanjiText: String = "" var jaImage: String = "" var engTitle: String = "" var engImage: String = "" } // MARK: - DATA struct WidgetSignData { static let list: [ListOfLanguage] = [ ListOfLanguage(headerTitle: "あ", hiraganaText: "あい", kanjiText: "愛", jaImage: "あい", engTitle: "Love", engImage: "Love"), ListOfLanguage(headerTitle: "あ", hiraganaText: "あん", kanjiText: "案", jaImage: "あん", engTitle: "Idea", engImage: "Idea"), ListOfLanguage(headerTitle: "あ", hiraganaText: "あいすくりーむ", kanjiText: "アイスクリーム", jaImage: "あいすくりーむ", engTitle: "Ice Cream", engImage: "Ice Cream") ] } // MARK: - STORAGE class QuizStorage { static let selectedKey = "selectedAnswer" static let questionKey = "questionID" static let itemKey = "currentItem" static var selected: String? { get { UserDefaults.standard.string(forKey: selectedKey) } set { UserDefaults.standard.set(newValue, forKey: selectedKey) } } static var questionID: String? { get { UserDefaults.standard.string(forKey: questionKey) } set { UserDefaults.standard.set(newValue, forKey: questionKey) } } static var currentItemIndex: Int? { get { let value = UserDefaults.standard.integer(forKey: itemKey) return value == 0 ? nil : value } set { UserDefaults.standard.set(newValue ?? 0, forKey: itemKey) } } } // MARK: - INTENT struct SelectAnswerIntent: AppIntent { static var title: LocalizedStringResource = "Select Answer" @Parameter(title: "Answer") var answer: String @Parameter(title: "QuestionID") var questionID: String init() {} init(answer: String, questionID: String) { self.answer = answer self.questionID = questionID } func perform() async throws -> some IntentResult { QuizStorage.selected = answer QuizStorage.questionID = questionID WidgetCenter.shared.reloadAllTimelines() DispatchQueue.main.asyncAfter(deadline: .now() + 5) { QuizStorage.selected = nil QuizStorage.questionID = nil QuizStorage.currentItemIndex = nil // 🔥 IMPORTANT WidgetCenter.shared.reloadAllTimelines() } return .result() } } // MARK: - ENTRY import WidgetKit struct QuizEntry: TimelineEntry { let date: Date let item: ListOfLanguage let options: [String] let correct: String let selected: String? let questionID: String } // MARK: - PROVIDER struct Provider: TimelineProvider { func placeholder(in context: Context) -> QuizEntry { sample() } func getSnapshot(in context: Context, completion: @escaping (QuizEntry) -> ()) { completion(sample()) } func getTimeline(in context: Context, completion: @escaping (Timeline<QuizEntry>) -> ()) { completion(Timeline(entries: [generate()], policy: .never)) } func generate() -> QuizEntry { let all = WidgetSignData.list // 🔥 If user already tapped → KEEP SAME QUESTION if let index = QuizStorage.currentItemIndex, let selected = QuizStorage.selected, let questionID = QuizStorage.questionID { let item = all[index] let correct = item.engTitle let wrongs = all .filter { $0.engTitle != correct } .shuffled() .prefix(2) .map { $0.engTitle } let options = ([correct] + wrongs).shuffled() return QuizEntry( date: Date(), item: item, options: options, correct: correct, selected: selected, questionID: questionID ) } // 🔥 Otherwise → create NEW quiz let item = all.randomElement()! let index = all.firstIndex(of: item)! let correct = item.engTitle let wrongs = all .filter { $0.engTitle != correct } .shuffled() .prefix(2) .map { $0.engTitle } let options = ([correct] + wrongs).shuffled() let questionID = UUID().uuidString // SAVE current quiz QuizStorage.currentItemIndex = index QuizStorage.questionID = questionID return QuizEntry( date: Date(), item: item, options: options, correct: correct, selected: nil, questionID: questionID ) } func sample() -> QuizEntry { QuizEntry( date: Date(), item: WidgetSignData.list[0], options: ["Love", "Idea", "Ice Cream"], correct: "Love", selected: nil, questionID: UUID().uuidString ) } } // MARK: - TEXT ATTRIBUTEDSTRING import SwiftUI func styledQuestion(_ kanji: String) -> AttributedString { var full = AttributedString("How do you sign \(kanji) in ASL?") if let r = full.range(of: "How do you sign ") { full[r].foregroundColor = .gray full[r].font = .system(size: 15) } if let r = full.range(of: kanji) { full[r].font = .system(size: 17, weight: .semibold) } if let r = full.range(of: " in ASL?") { full[r].foregroundColor = .gray full[r].font = .system(size: 15) } return full } // MARK: - WIDGET struct QuizWidgetView_JP: View { var entry: QuizEntry var body: some View { VStack(spacing: 5) { HStack { Text("🇯🇵") Text(styledQuestion(entry.item.kanjiText)) .lineLimit(1) .minimumScaleFactor(0.7) Spacer() Image("Logo") .resizable() .frame(width: 23, height: 23) } HStack(alignment: .top, spacing: 10) { ZStack { Image("BG") .resizable() .scaledToFill() .clipShape(RoundedRectangle(cornerRadius: 17)) Image(entry.item.jaImage) .resizable() .scaledToFit() .padding(10) } .aspectRatio(1, contentMode: .fit) VStack(spacing: 8) { ForEach(entry.options, id: \.self) { option in Button(intent: SelectAnswerIntent(answer: option, questionID: entry.questionID)) { Text(option) .frame(maxWidth: .infinity) .padding(6) .background(buttonColor(option)) .clipShape(RoundedRectangle(cornerRadius: 10)) } .buttonStyle(.plain) } } } } .containerBackground(for: .widget) { Color.clear } .contentMargins(.all, 6) } func buttonColor(_ option: String) -> Color { guard QuizStorage.questionID == entry.questionID, let selected = entry.selected else { return Color.gray.opacity(0.2) } if option == entry.correct { return .green } if option == selected { return .red } return Color.gray.opacity(0.2) } } // MARK: - Button struct QuizButton: View { let title: String var body: some View { Text(title) .font(.system(size: 13, weight: .semibold)) // 👈 stronger text .frame(maxWidth: .infinity) .frame(height: 32) // 👈 THIS is the key (bigger tap area) .background( RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.18)) // 👈 more visible ) } }

And don't forget to add this code to WidgetBundle File

struct QuizWidget_JP: Widget { let kind: String = "QuizWidget_JP" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider()) { entry in QuizWidgetView_JP(entry: entry) // Japanese } .configurationDisplayName("クイズ(日本語)") .description("日本語で学ぶ") .supportedFamilies([.systemMedium]) } }

Screen Record result:

https://x.com/antonio07341176/status/2046523492296794156?s=20

This is so closer to success a quiz interactive widget! However, I need some your help with Button problems. Thank you!

Read Entire Article