Dark mode in SwiftUI Preview doesn't have a dark background with Xcode 11.4 - xcode

Does anyone have the same problem, that Xcode (11.4) doesn't show a dark background, when previewing in dark mode?
Steps to reproduce:
1) Create a new project, a Single View App
2) Add the .environment-modifier to the preview:
Group {
ContentView()
.environment(\.colorScheme, .light)
ContentView()
.environment(\.colorScheme, .dark)
}
You get this result:

Setting \.colorScheme in the environment is deprecated, so instead use the .preferedColorScheme modifier. For example,
ContentView()
.preferredColorScheme(.dark)

As m-reza-f mentioned in a similar question, this is a bug in Xcode (which is still active as this answer is posted).
I'll add that instead of wrapping your actual body code in a NavigationView, you can simply wrap the previews code in your PreviewProvider in a NavigationView instead, to achieve the same results:
struct ContentView_Previews: PreviewProvider {
Group {
NavigationView {
ContentView()
.environment(\.colorScheme, .light)
}
NavigationView {
ContentView()
.environment(\.colorScheme, .light)
}
}
}

try this:
#available(iOS 13.0, *)
public struct DarkView<Content> : View where Content : View {
var darkContent: Content
var on: Bool
public init(_ on: Bool, #ViewBuilder content: () -> Content) {
self.darkContent = content()
self.on = on
}
public var body: some View {
ZStack {
if on {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
darkContent.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.black).colorScheme(.dark)
} else {
darkContent
}
}
}
}
#available(iOS 13.0, *)
extension View {
#available(iOS 13.0, *)
public func darkModeFix(_ on: Bool = true) -> DarkView<Self> {
DarkView(on) {
self
}
}
}
and then
yourView()
.environment(\.colorScheme, .dark)
.darkModeFix()

Related

Customizing the macOS toolbar with Swift UI

I'm working on a macOS app, with the view layers written in SwiftUI. I know that iOS toolbars can have the background color changed at least, but when I try to do this in macOS, it doesn't behave as I'd expect.
Here's a (simplified) example:
struct ContentView: View {
var body: some View {
NavigationView {
Collections()
.layoutPriority(0)
Photos()
.frame(maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
.background(Color.Alt.black)
.layoutPriority(1)
}
.toolbar {
Toolbar().background(Color.red500)
}
}
}
struct Toolbar: View {
var body: some View {
Group {
Slider(value: 250, in: 150...400) {
Text("Toolbar.PreviewSize")
} minimumValueLabel: {
Image(systemName: "photo").resizable().scaledToFit().frame(width: 15)
} maximumValueLabel: {
Image(systemName: "photo").resizable().scaledToFit().frame(width: 23)
} onEditingChanged: { _ in
// do nothing
}.frame(minWidth: 200)
Spacer()
Text("Toolbar.SelectionCount")
Spacer()
AddPhotosButton()
}
}
}
Which produces something like this, which as you can see, doesn't apply the background color to the entire toolbar, just to the items in the toolbar:
I'm guessing I could make my own WindowToolbarStyle style, but there's no documentation on the protocol!
If I make my own toolbar as a View rather than with the .toolbar modifier, I can't read the safe area insets for the window traffic buttons when the sidebar is collapsed, resulting in a complete mess:
Thanks for any help!
I recommend
{
.toolbar {
Toolbar()
}.toolbarBackground(Color.gray)
}

How do I switch between screens in TabView and from the latter to the other View?

I created a simple collection with a button jump to the next View. From the last View there should be a transition to AddItemView, but it doesn't happen - it goes back to the first screen.
Can you tell me where I made a mistake?
What is the correct way to place the background Image on the first collection screen, so that it won't be on the following screens?
import SwiftUI
struct AddItemView: View {
var body: some View {
Text("Hallo!")
}
}
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
ForEach(0..<emojis.endIndex) { index in
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.endIndex {
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In this code, you have not to use NavigationView. It's required to navigate to the next screen. Similar concept like Push view controller if navigation controller exists. Also, remove endIndex and use indices.
struct ContentView: View {
var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple ]
var emojis: [String] = [ "👻", "🐱", "🦊" , "👺", "🎃"]
#State private var tabSelection = 0
var body: some View {
NavigationView { //<- add navigation view
TabView(selection: $tabSelection) {
ForEach(emojis.indices) { index in //<-- use indices
VStack {
Text(emojis[index])
.font(.system(size: 150))
.frame(minWidth: 30, maxWidth: .infinity, minHeight: 0, maxHeight: 250)
.background(colors[index])
.clipShape(RoundedRectangle(cornerRadius: 30))
.padding()
.tabItem {
Text(emojis[index])
}
Button(action: {
self.tabSelection += 1
}) {
if tabSelection == emojis.count - 1 { //<- use count
NavigationLink(destination: AddItemView()) {
Text("Open View")
}
} else {
Text("Change to next tab")
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
}
}
}
If you have already a navigation link from the previous screen then, the problem is you are using endIndex in the wrong way. Check this thread for correct use (https://stackoverflow.com/a/36683863/14733292).

macOS SwiftUI Navigation for a Single View

I'm attempting to create a settings view for my macOS SwiftUI status bar app. My implementation so far has been using a NavigationView, and NavigationLink, but this solution produces a half view as the settings view pushes the parent view to the side. Screenshot and code example below.
Navigation Sidebar
struct ContentView: View {
var body: some View {
VStack{
NavigationView{
NavigationLink(destination: SecondView()){
Text("Go to next view")
}}
}.frame(width: 800, height: 600, alignment: .center)}
}
struct SecondView: View {
var body: some View {
VStack{
Text("This is the second view")
}.frame(width: 800, height: 600, alignment: .center)
}
}
The little information I can find suggests that this is unavoidable using SwiftUI on macOS, because the 'full screen' NavigationView on iOS (StackNavigationViewStyle) is not available on macOS.
Is there a simple or even complex way of implementing a transition to a settings view that takes up the whole frame in SwiftUI for macOS? And if not, is it possible to use AppKit to call a View object written in SwiftUI?
Also a Swift newbie - please be gentle.
Here is a simple demo of possible approach for custom navigation-like solution. Tested with Xcode 11.4 / macOS 10.15.4
Note: background colors are used for better visibility.
struct ContentView: View {
#State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
struct NextView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Back") { self.show = false }
Text("This is the second view")
}
}
}
I've expanded upon Asperi's great suggestion and created a generic, reusable StackNavigationView for macOS (or even iOS, if you want). Some highlights:
It supports any number of subviews (in any layout).
It automatically adds a 'Back' button for each subview (just text for now, but you can swap in an icon if using macOS 11+).
Swift v5.2:
struct StackNavigationView<RootContent, SubviewContent>: View where RootContent: View, SubviewContent: View {
#Binding var currentSubviewIndex: Int
#Binding var showingSubview: Bool
let subviewByIndex: (Int) -> SubviewContent
let rootView: () -> RootContent
var body: some View {
VStack {
VStack{
if !showingSubview { // Root view
rootView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if showingSubview { // Correct subview for current index
StackNavigationSubview(isVisible: self.$showingSubview) {
self.subviewByIndex(self.currentSubviewIndex)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
init(currentSubviewIndex: Binding<Int>, showingSubview: Binding<Bool>, #ViewBuilder subviewByIndex: #escaping (Int) -> SubviewContent, #ViewBuilder rootView: #escaping () -> RootContent) {
self._currentSubviewIndex = currentSubviewIndex
self._showingSubview = showingSubview
self.subviewByIndex = subviewByIndex
self.rootView = rootView
}
private struct StackNavigationSubview<Content>: View where Content: View {
#Binding var isVisible: Bool
let contentView: () -> Content
var body: some View {
VStack {
HStack { // Back button
Button(action: {
self.isVisible = false
}) {
Text("< Back")
}.buttonStyle(BorderlessButtonStyle())
Spacer()
}
.padding(.horizontal).padding(.vertical, 4)
contentView() // Main view content
}
}
}
}
More info on #ViewBuilder and generics used can be found here.
Here's a basic example of it in use. The parent view tracks current selection and display status (using #State), allowing anything inside its subviews to trigger state changes.
struct ExampleView: View {
#State private var currentSubviewIndex = 0
#State private var showingSubview = false
var body: some View {
StackNavigationView(
currentSubviewIndex: self.$currentSubviewIndex,
showingSubview: self.$showingSubview,
subviewByIndex: { index in
self.subView(forIndex: index)
}
) {
VStack {
Button(action: { self.showSubview(withIndex: 0) }) {
Text("Show View 1")
}
Button(action: { self.showSubview(withIndex: 1) }) {
Text("Show View 2")
}
Button(action: { self.showSubview(withIndex: 2) }) {
Text("Show View 3")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}
}
private func subView(forIndex index: Int) -> AnyView {
switch index {
case 0: return AnyView(Text("I'm View One").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.green))
case 1: return AnyView(Text("I'm View Two").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.yellow))
case 2: return AnyView(VStack {
Text("And I'm...")
Text("View Three")
}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.orange))
default: return AnyView(Text("Inavlid Selection").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red))
}
}
private func showSubview(withIndex index: Int) {
currentSubviewIndex = index
showingSubview = true
}
}
Note: Generics like this require all subviews to be of the same type. If that's not so, you can wrap them in AnyView, like I've done here. The AnyView wrapper isn't required if you're using a consistent type for all subviews (the root view’s type doesn’t need to match).
Heyo, so a problem I had is that I wanted to have multiple navigationView-layers, I'm not sure if that's also your attempt, but if it is: MacOS DOES NOT inherit the NavigationView.
Meaning, you need to provide your DetailView (or SecondView in your case) with it's own NavigationView. So, just embedding like [...], destination: NavigationView { SecondView() }) [...] should do the trick.
But, careful! Doing the same for iOS targets will result in unexpected behaviour. So, if you target both make sure you use #if os(macOS)!
However, when making a settings view, I'd recommend you also look into the Settings Scene provided by Apple.
Seems this didn't get fixed in Xcode 13.
Tested on Xcode 13 Big Sur, not on Monterrey though...
You can get full screen navigation with
.navigationViewStyle(StackNavigationViewStyle())

In a MacOS App using SwiftUI, how to modify the default blue background of a selected NavigationLink nested in a list?

Good day all. In a MacOS App using SwiftUI, how to modify the default blue background of a selected NavigationLink nested in a list? The list is inside a NavigationView. I could not find the solution here. What would be the code to add to the following basic exemple: Two TextView listed, and if we click on the TextView we display the correspondant View.
ContentView.swift:
import SwiftUI
struct ContentView: View {
#State var selection: Int?
var body: some View {
HStack() {
NavigationView {
List () {
NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
Text("Click Me To Display The First View")
} // End Navigation Link
NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
Text("Click Me To Display The Second View")
} // End Navigation Link
} // End list
.frame(minWidth: 350, maxWidth: 350)
.onAppear {
self.selection = 0
}
} // End NavigationView
.listStyle(SidebarListStyle())
.frame(maxWidth: .infinity, maxHeight: .infinity)
} // End HStack
} // End some View
} // End ContentView
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
FirstView.swift:
import SwiftUI
struct FirstView: View {
var body: some View {
Text("(1) Hello, I am the first view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift:
import SwiftUI
struct SecondView: View {
var body: some View {
Text("(2) Hello, I am the second View")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
Here is the result if we click on the first row... the background is blue when selected! How to change this default color? Thanks in advance for your help.
the following code is one option to achieve what you want:
struct ContentView: View {
#State var selection: Int? = 0
#State var pressed: Int? = 0
init() {
UITableViewCell.appearance().selectionStyle = .none
UITableView.appearance().backgroundColor = UIColor.clear
}
var body: some View {
var theBinding = Binding(
get: { self.selection },
set: {
self.selection = $0
self.pressed = $0 == nil ? self.pressed : $0!
})
return HStack() {
NavigationView {
List {
NavigationLink(destination: FirstView(), tag: 0, selection: theBinding) {
Text("Click Me To Display The First View")
}.listRowBackground(self.pressed == 0 ? Color.red : Color.green)
NavigationLink(destination: SecondView(), tag: 1, selection: theBinding) {
Text("Click Me To Display The Second View")
}.listRowBackground(self.pressed == 1 ? Color.red : Color.green)
}.frame(minWidth: 350, maxWidth: 350)
.onAppear { self.pressed = 0 }
}.listStyle(SidebarListStyle())
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}

SwiftUI for MacOS (not Catalyst) - how to add buttons to navigation bar?

Goal:
Add buttons to navigation bar on a real SwiftUI for MacOS (not Catalyst)
What I did:
Tried navigationBarItem, but found it's not available for MacOS.
Does anyone have a solution? Browsed all possible MacOS SwiftUi projects and didn't find anything simulation. Should be trivial, as this is a very common use case?
import SwiftUI
struct SwiftUIView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.navigationBarItems()
}
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
There is no such thing in macOS. You could use the NavigationLink.
Check the following code:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Detail").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}

Resources