This is my first time replying to a forum post, so bear with me haha.
If you want to unit test views, you need to hold all their data in viewModels (which is the recommended architecture, so you should always do that anyways). So create a viewmodel for your view that would hold the gameType variable and then create a function that would either set it to 1 or 2 (if you only need 2 options for your radio buttons, just hold the info in a bool | if you need more options, use an enum, that way you leave no space for weird errors when you mistype an Int).
So here's your viewModel:
class YourViewModel: ObservableObject {
@Published var gameType: Int = 1
func changeGameType(to number: Int) {
DispatchQueue.main.async { // Important! View updates should only be made from main thread
self.gameType = number
}
}
}
}
Your view:
struct YourView: View {
@ObservedObject private var viewModel = YourViewModel()
var body: some View {
YourButtonView(title: "Change Game Type to 2", selectedGameType: $viewModel.gameType) {
viewModel.changeGameType(to: 2)
}
}
}
Your button view: (not a radio button obvs, adapt to needs)
struct YourButtonView: View {
let title: String
let action: () -> Void
@Binding var selectedGameType: Int
init(title: String, selectedGameType: Binding<Int>, action: @escaping () -> Void) {
self.title = title
self._selectedGameType = selectedGameType
self.action = action
}
var body: some View {
Button(title, action: action)
}
}
And the unit test:
func testGameType() {
// Note: Since we used DispatchQueue.main.async in our function testing the function as if it was a synchronous one will fail!
// So we will use Combine instead
// Given
let viewModel = YourViewModel()
let expectation = XCTestExpectation(description: "Changes game type to 2.")
var cancellable: AnyCancellable?
cancellable = viewModel.$gameType
.dropFirst() // We remove the inital value (in our case 1)
.sink { gameType in
// Assert new gameType value
XCTAssert(
gameType == 2,
"Value expected to be 2, but got \(gameType) instead"
)
// Fulfill expectation
expectation.fulfill()
}
// When ( We imitate the radio button press by calling its function )
viewModel.changeGameType(to: 2)
// Then
wait(for: [expectation], timeout: 1)
}
Post
Replies
Boosts
Views
Activity
UI Elements (as in UIImage) are from UIKit which is the framework for iOS. Its equivalent for macOS is AppKit which uses NS Elements (as in NSImage). Lots of times they work in extremely similar ways. So you can use NSImage same way you would UIImage -> just do NSImage(data: data) to initalize an NSImage and then use it in SwiftUI with Image(nsImage: NSImage). If you're super used to typing out UIImage every time you could even do a typealias like this: typealias UIImage = NSImage to keep using the term UIImage in your macOS application.
FYI: this forum is for code-related questions. But to try and answer your question, you could click on "Membership Details" and then set "Auto-renew" to enabled. It will then renew automatically without you having to do anything. Just don't forget to check that your debit/credit card details are up to date.
I've never figured out how to make forced color themes work in pure SwiftUI but this is what I did for one of my apps that worked fine for me. ( I didn't try it specifically from a sheet but I'd be very confused if it didn't work
Note: This is for the whole app, not only for one view.
// Create an enum case for every theme
enum ColorTheme: String, CaseIterable {
case light
case dark
case system
}
@main
struct ExampleApp: App {
// Save user's choice in preferences (userdefaults)
@AppStorage("colorTheme") private var colorTheme: ColorTheme = .system
var body: some Scene {
WindowGroup {
SplashScreen()
.onAppear {
overrideColorTheme()
}
.onChange(of: colorTheme) { _ in
overrideColorTheme()
}
}
}
func overrideColorTheme() {
var userInterfaceStyle: UIUserInterfaceStyle
switch colorTheme {
case .light:
userInterfaceStyle = .light
case .dark:
userInterfaceStyle = .dark
case .system:
userInterfaceStyle = .unspecified
}
UIApplication.shared.windows.first?.overrideUserInterfaceStyle = userInterfaceStyle
}
}
Now from anywhere in your app you can update the value stored in UserDefaults and it will update the color theme used in your whole app.
Like so:
struct ExampleView: View {
@AppStorage("colorTheme") private var colorTheme: ColorTheme = .system
var body: some View {
VStack {
Button("Set to Light") { colorTheme = .light }
Button("Set to Dark") { colorTheme = .dark }
Button("Set to System") { colorTheme = .system }
}
}
}
Just checked and saw that since iOS 15.0 UIApplication.shared.windows is deprecated so you could use an extension like this for 15 and above
extension UIApplication {
func setColorTheme(to theme: UIUserInterfaceStyle) {
let keyWindow = self.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
keyWindow?.overrideUserInterfaceStyle = theme
}
}
And use it like so in the previous mentioned overrideColorTheme() function:
UIApplication.shared.setColorTheme(to: userInterfaceStyle)
From my understanding of your question I think what you're looking for is ScrollView.
Just wrap all the views in a ScrollView and you'll get one coherent, scrollable view.
Like so:
@AppStorage("MediAnsicht") var MedisEinfacheAnsicht = false
var body: some View {
if MedisEinfacheAnsicht == true {
ScrollView {
AcetylsalicylsaeureAllgInfos()
AcetylsalicylsaeureKontraind()
AcetylsalicylsaeureUAWs()
}
} else {
AcetylsalicylsaeureMenu()
}
}
}