I need to use a Picker view but I don't see any options to hide the green focus border.
Code:
#State private var selectedIndex = 0
var values: [String] = (0 ... 12).map { String($0) }
var body: some View {
Picker(selection: $selectedIndex, label: Text("")) {
ForEach(0 ..< values.count) {
Text(values[$0])
}
}
.labelsHidden()
}
The following extension puts a black overlay over the picker border.
Result
Code
extension Picker {
func focusBorderHidden() -> some View {
let isWatchOS7: Bool = {
if #available(watchOS 7, *) {
return true
}
return false
}()
let padding: EdgeInsets = {
if isWatchOS7 {
return .init(top: 17, leading: 0, bottom: 0, trailing: 0)
}
return .init(top: 8.5, leading: 0.5, bottom: 8.5, trailing: 0.5)
}()
return self
.overlay(
RoundedRectangle(cornerRadius: isWatchOS7 ? 8 : 7)
.stroke(Color.black, lineWidth: isWatchOS7 ? 4 : 3.5)
.offset(y: isWatchOS7 ? 0 : 8)
.padding(padding)
)
}
}
Usage
Make sure .focusBorderHidden() is the first modifier.
Picker( [...] ) {
[...]
}
.focusBorderHidden()
[...]
On the Picker, something like this can be added to cover up the green border.
#ScaledMetric var borderWidth: CGFloat = 5 // or it can be 3
Picker {
...
}.border(Color.black, width: borderWidth)
Adding a mask of cornerRadius of whatever required but with a padding of 2 or over (so as to mask the outer edge of the view) as the green border width on the watch tends to be around 2... will do the trick
.mask(RoundedRectangle(cornerRadius: 12).padding(2))
I like the border method from Ray Hunter too but to keep things tidy and simple I rather stay away from having lots and lots of #... variables
Related
I would like to add animating views to a parent view. I know that the parent view needs to position the children but I'm having trouble coming up with the formula to implement. I have the first couple of views right but once I get to 4 and up its a problem! I would like the views to appear in a grid with 3 columns.
Here is some reproducible code ready to be copy and pasted.
import SwiftUI
struct CustomView: View, Identifiable {
#State private var startAnimation = false
let id = UUID()
var body: some View {
Circle()
.frame(width: 50, height: 50)
.scaleEffect(x: startAnimation ? 2 : 1,
y: startAnimation ? 2 : 1)
.animation(Animation.interpolatingSpring(mass: 2, stiffness: 20, damping: 1, initialVelocity: 1))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.startAnimation = true
}
}
}
}
struct StartView: View {
#State private var userSelection: [CustomView] = []
var body: some View {
VStack(spacing: -20) {
Button("Add View") {
self.userSelection.append(CustomView())
}
LazyVGrid(columns: gridStyle) {
ForEach(Array(userSelection.enumerated()), id: \.0 ){ index, equip in
CustomView()
.position(x: widthBasedOn(index: index), y: heightBasedOn(index: index))
}
.padding([])
}
.frame(width: UIScreen.main.bounds.width * 0.5,
height: UIScreen.main.bounds.height * 0.8)
}
}
let gridStyle = [
GridItem(.flexible(minimum: 0, maximum: 100), spacing: -50),
GridItem(.flexible(minimum: 0, maximum: 100), spacing: -50),
GridItem(.flexible(minimum: 0, maximum: 100), spacing: -50)
]
private func widthBasedOn(index: Int) -> CGFloat {
if index % 3 != 0 {
if index > 3 {
let difference = index - 4
return CGFloat(index * difference * 100)
}
let answer = CGFloat(index * 100)
print("\(index) width should be: \(answer)")
return answer
}
return 0
}
private func heightBasedOn(index: Int) -> CGFloat {
if index > 3 && index < 6 {
return 100
}
return 200
}
}
struct EquipmentSelectionView_Previews: PreviewProvider {
static var previews: some View {
StartView()
}
}
Since most of your question is somewhat vague, and I am not sure about the specifics, this is my solution. Feel free to respond, and I will be glad to answer your question further with more tailored solution.
I removed many of your code that was unnecessary or overly-complicated. For example, I removed the widthBasedOn and heightBasedOn methods. I also changed the array property var userSelection: [CustomView] to var numberOfViews = 0.
Note: Both your original code and my solution cause all the circles to wiggle up and down, whenever a new circle is added.
I suggest that you copy paste this code snippet, run it in Xcode, and see if this is what you want.
struct CustomView: View, Identifiable {
#State private var startAnimation = false
let id = UUID()
var body: some View {
Circle()
//Changing the frame size of the circle, making it bigger or smaller
.frame(width: startAnimation ? 100 : 50, height: startAnimation ? 100 : 50)
.animation(Animation.interpolatingSpring(mass: 2, stiffness: 20, damping: 1, initialVelocity: 1))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.startAnimation = true
}
}
}
}
struct StartView: View {
//View will display this number of circles
#State private var numberOfViews = 0
var body: some View {
VStack() {
Button("Add View") {
self.numberOfViews += 1
}
.padding(.top, 100)
Spacer()
LazyVGrid(columns: gridStyle) {
//Add a new circle CustomView() to the LazyVGrid for each number of views
ForEach(0..<numberOfViews, id: \.self ){view in
CustomView()
}
}
}
}
//3 columns, flexible spacing for elments. In this case, equal amount of spacing.
let gridStyle = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
]
}
struct EquipmentSelectionView_Previews: PreviewProvider {
static var previews: some View {
StartView()
}
}
Limiting number of circles
To limit the number of circles:
if numberOfViews < 9 {
self.numberOfViews += 1
}
Positioning the button
To position the button, you can add padding:
Button("Add View") {
if numberOfViews < 9 {
self.numberOfViews += 1
}
}
.padding(.top, 100)
Overlap vs. No Overlap
Using there .frame modifier will not have any overlap:
.frame(width: startAnimation ? 100 : 50, height: startAnimation ? 100 : 50)
But if you do want overlap, use .scaleEffect:
.scaleEffect(x: startAnimation ? 2 : 1,
y: startAnimation ? 2 : 1)
P.S. Unfortunately, I can't show you the results with GIF images because Stackoverflow keep giving me upload errors.
Inside of a ZStack:
I know you can set up a VStack inside a .mask(), but then I have to add offsets to each object so they don't overlap.
Is there a way to modify your object to mask a specific view inside a single VStack?
So you probably want either the left column or the right column of this demo:
In the left column, the gradient spans all of the bubbles, including the bubbles off the screen. So the gradient appears to scroll with the bubbles.
In the right column, the gradient spans just the visible frame of the scroll view, so the bubble backgrounds appear to change as the bubbles move up and down.
Either way, this is a tricky problem! I've solved it before for UIKit. Here's a solution for SwiftUI.
Here's what we'll do:
Draw each “bubble” (rounded rectangle) with a clear (transparent) background.
Record the frame (in global coordinates) of each bubble in a “preference”. A preference is SwiftUI's API for passing values from child views to ancestor views.
Add a background to some common ancestor of all the bubble views. The background draws the gradient big enough to cover the entire common ancestor, but masked to only be visible in a rounded rectangle under each bubble.
We'll collect the frames of the bubbles in this data structure:
struct BubbleFramesValue {
var framesForKey: [AnyHashable: [CGRect]] = [:]
var gradientFrame: CGRect? = nil
}
We'll collect the frames of the bubbles in the framesForKey property. Since we want to draw two gradients (gold and teal), we need to keep separate collections of bubble frames. So framesForKey[gold] collects the frames of the gold bubbles, and framesForKey[teal] collects the frames of the teal bubbles.
We'll also need the frame of the common ancestor, so we'll store that in the gradientFrame property.
We'll collect these frames using the preference API, which means we need to define a type that conforms to the PreferenceKey protocol:
struct BubbleFramesKey { }
extension BubbleFramesKey: PreferenceKey {
static let defaultValue: BubbleFramesValue = .init()
static func reduce(value: inout BubbleFramesValue, nextValue: () -> BubbleFramesValue) {
let next = nextValue()
switch (value.gradientFrame, next.gradientFrame) {
case (nil, .some(let frame)): value.gradientFrame = frame
case (_, nil): break
case (.some(_), .some(_)): fatalError("Two gradient frames defined!")
}
value.framesForKey.merge(next.framesForKey) { $0 + $1 }
}
}
Now we can define two new methods on View. The first method declares that the view should be a bubble, meaning it should have a rounded rect background that shows the gradient. This method uses the preference modifier and a GeometryReader to supply its own frame (in global coordinates) as a BubbleFramesValue:
extension View {
func bubble<Name: Hashable>(named name: Name) -> some View {
return self
.background(GeometryReader { proxy in
Color.clear
.preference(
key: BubbleFramesKey.self,
value: BubbleFramesValue(
framesForKey: [name: [proxy.frame(in: .global)]],
gradientFrame: nil))
})
}
}
The other method declares that the view is the common ancestor of bubbles, and so it should define the gradientFrame property. It also inserts the background gradient behind itself, with the appropriate mask made of RoundedRectangles:
extension View {
func bubbleFrame(
withGradientForKeyMap gradientForKey: [AnyHashable: LinearGradient]
) -> some View {
return self
.background(GeometryReader { proxy in
Color.clear
.preference(
key: BubbleFramesKey.self,
value: BubbleFramesValue(
framesForKey: [:],
gradientFrame: proxy.frame(in: .global)))
} //
.edgesIgnoringSafeArea(.all))
.backgroundPreferenceValue(BubbleFramesKey.self) {
self.backgroundView(for: $0, gradientForKey: gradientForKey) }
}
private func backgroundView(
for bubbleDefs: BubbleFramesKey.Value,
gradientForKey: [AnyHashable: LinearGradient]
) -> some View {
return bubbleDefs.gradientFrame.map { gradientFrame in
GeometryReader { proxy in
ForEach(Array(gradientForKey.keys), id: \.self) { key in
bubbleDefs.framesForKey[key].map { bubbleFrames in
gradientForKey[key]!.masked(
toBubbleFrames: bubbleFrames, inGradientFrame: gradientFrame,
readerFrame: proxy.frame(in: .global))
}
}
}
}
}
}
We set up the gradient to have the correct size, position, and mask in the masked(toBubbleFrames:inGradientFrame:readerFrame:) method:
extension LinearGradient {
fileprivate func masked(
toBubbleFrames bubbleFrames: [CGRect],
inGradientFrame gradientFrame: CGRect,
readerFrame: CGRect
) -> some View {
let offset = CGSize(
width: gradientFrame.origin.x - readerFrame.origin.x,
height: gradientFrame.origin.y - readerFrame.origin.y)
let transform = CGAffineTransform.identity
.translatedBy(x: -readerFrame.origin.x, y: -readerFrame.origin.y)
var mask = Path()
for bubble in bubbleFrames {
mask.addRoundedRect(
in: bubble,
cornerSize: CGSize(width: 10, height: 10),
transform: transform)
}
return self
.frame(
width: gradientFrame.size.width,
height: gradientFrame.size.height)
.offset(offset)
.mask(mask)
}
}
Whew! Now we're ready to try it out. Let's write the ContentView I used for the demo at the top of this answer. We'll start by defining a gold gradient and a teal gradient:
struct ContentView {
init() {
self.gold = "gold"
self.teal = "teal"
gradientForKey = [
gold: LinearGradient(
gradient: Gradient(stops: [
.init(color: Color(#colorLiteral(red: 0.9823742509, green: 0.8662455082, blue: 0.4398147464, alpha: 1)), location: 0),
.init(color: Color(#colorLiteral(red: 0.3251565695, green: 0.2370383441, blue: 0.07140993327, alpha: 1)), location: 1),
]),
startPoint: UnitPoint(x: 0, y: 0),
endPoint: UnitPoint(x: 0, y: 1)),
teal: LinearGradient(
gradient: Gradient(stops: [
.init(color: Color(#colorLiteral(red: 0, green: 0.8077999949, blue: 0.8187007308, alpha: 1)), location: 0),
.init(color: Color(#colorLiteral(red: 0.08204867691, green: 0.2874087095, blue: 0.4644176364, alpha: 1)), location: 1),
]),
startPoint: UnitPoint(x: 0, y: 0),
endPoint: UnitPoint(x: 0, y: 1)),
]
}
private let gold: String
private let teal: String
private let gradientForKey: [AnyHashable: LinearGradient]
}
We'll want some content to show in the bubbles:
extension ContentView {
private func bubbledItem(_ i: Int) -> some View {
Text("Bubble number \(i)")
.frame(height: 60 + CGFloat((i * 19) % 60))
.frame(maxWidth: .infinity)
.bubble(named: i.isMultiple(of: 2) ? gold : teal)
.padding([.leading, .trailing], 20)
}
}
Now we can define the body of ContentView. We draw VStacks of bubbles, each inside a scroll view. On the left side, I put the bubbleFrame modifier on the VStack. On the right side, I put the bubbleFrame modifier on the ScrollView.
extension ContentView: View {
var body: some View {
HStack(spacing: 4) {
ScrollView {
VStack(spacing: 8) {
ForEach(Array(0 ..< 20), id: \.self) { i in
self.bubbledItem(i)
}
} //
.bubbleFrame(withGradientForKeyMap: gradientForKey)
} //
ScrollView {
VStack(spacing: 8) {
ForEach(Array(0 ..< 20), id: \.self) { i in
self.bubbledItem(i)
}
}
} //
.bubbleFrame(withGradientForKeyMap: gradientForKey)
}
}
}
And here's the PreviewProvider so we can see it in Xcode's canvas:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
On few screens of my SwiftUI app I am using Divider() between few elements. This Divider is rendered as a very thin grey (or black?) line. I am guessing 1 point. How can I change the width of Divider()?
You can create any divider you wish, colors, width, content... As in below example.
struct ExDivider: View {
let color: Color = .gray
let width: CGFloat = 2
var body: some View {
Rectangle()
.fill(color)
.frame(height: width)
.edgesIgnoringSafeArea(.horizontal)
}
}
For anyone interested, I modified accepted answer with support for both horizontal and vertical directions, as well as used original Divider colors with support for Dark Mode:
struct ExtendedDivider: View {
var width: CGFloat = 2
var direction: Axis.Set = .horizontal
#Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
Rectangle()
.fill(colorScheme == .dark ? Color(red: 0.278, green: 0.278, blue: 0.290) : Color(red: 0.706, green: 0.706, blue: 0.714))
.applyIf(direction == .vertical) {
$0.frame(width: width)
.edgesIgnoringSafeArea(.vertical)
} else: {
$0.frame(height: width)
.edgesIgnoringSafeArea(.horizontal)
}
}
}
}
Bonus, this snippet uses applyIf syntax:
extension View {
#ViewBuilder func applyIf<T: View>(_ condition: #autoclosure () -> Bool, apply: (Self) -> T, else: (Self) -> T) -> some View {
if condition() {
apply(self)
} else {
`else`(self)
}
}
}
This worked for me:
Divider().frame(maxWidth: 100)
That was about a third of the width of the screen on portrait mode on an iPhone 11.
This is way late but...adding a background color to height will present a thicker Divider(). The divider is still razor thin within the image but the desired effect works.
Divider().frame(width: 300,height: 10).background(Color.blue)
I found this works best...
Group {
Divider()
.frame(height: 2.0)
.background(Color.blue)
}
.frame(width: 100)
once you place the Divider into the group, you can alter the surrounding area of the Divider by altering the Group
This would make the divider line 2 px high, and 100 px long
You can adjust the thickness of a Divider using frame(width, height) depending on if it is a vertical or horizontal Divider.
If you have a vertical Divider, then you can use frame(height: ?) to adjust the thickness. If you have a horizontal Divider, then you can use frame(width: ?). Along with setting the frame width or height you will also need to user the overlay modifier to give the Divider a color (even if you leave it grey).
example:
Divider()
.frame(height: 5)
.overlay(.gray)
*See this blog for more Divider customization
https://sarunw.com/posts/swiftui-divider/#:~:text=of%20a%20divider.-,Adjust%20SwiftUI%20Divider%20Thickness,-SwiftUI%20divider%20has
struct AppDivider: View {
var color: Color =.gray
var lineWidth: CGFloat = 1
var body: some View {
Divider()
.frame(height: lineWidth)
.overlay(color)
}
}
struct AppDivider_Previews: PreviewProvider {
static var previews: some View {
VStack {
AppDivider()
AppDivider(color: .red, lineWidth: 5)
}
}
}
I am making a custom Picker in the SegmentedPickerStyle(). I want to have the same behaviour but when I tap on the area between the content and the border of one of the possible selections the onTapGesture does not work. When I add a blue background it does work but with a clear background it doesn't.
Working with blue background
Not working with clear background
Not working code:
import SwiftUI
struct PickerElementView<Content>: View where Content : View {
#Binding var selectedElement: Int
let content: () -> Content
#inlinable init(_ selectedElement: Binding<Int>, #ViewBuilder content: #escaping () -> Content) {
self._selectedElement = selectedElement
self.content = content
}
var body: some View {
GeometryReader { proxy in
self.content()
.fixedSize(horizontal: true, vertical: true)
.frame(minWidth: proxy.size.width, minHeight: proxy.size.height)
// ##################################################################
// CHANGE COLOR HERE TO BLUE TO MAKE IT WORK
// ##################################################################
.background(Color.clear)
// ##################################################################
.border(Color.yellow, width: 5)
}
}
}
struct PickerView: View {
#Environment (\.colorScheme) var colorScheme: ColorScheme
var elements: [(id: Int, view: AnyView)]
#Binding var selectedElement: Int
#State var internalSelectedElement: Int = 0
private var width: CGFloat = 220
private var height: CGFloat = 100
private var cornerRadius: CGFloat = 20
private var factor: CGFloat = 0.95
private var color = Color(UIColor.systemGray)
private var selectedColor = Color(UIColor.systemGray2)
init(_ selectedElement: Binding<Int>) {
self._selectedElement = selectedElement
self.elements = [
(id: 0, view: AnyView(PickerElementView(selectedElement) {
Text("9").font(.system(.title))
})),
(id: 1, view: AnyView(PickerElementView(selectedElement) {
Text("5").font(.system(.title))
})),
]
self.internalSelectedElement = selectedElement.wrappedValue
}
func calcXPosition() -> CGFloat {
var pos = CGFloat(-self.width * self.factor / 4)
pos += CGFloat(self.internalSelectedElement) * self.width * self.factor / 2
return pos
}
var body: some View {
ZStack {
Rectangle()
.foregroundColor(self.selectedColor)
.cornerRadius(self.cornerRadius * self.factor)
.frame(width: self.width * self.factor / CGFloat(self.elements.count), height: self.height - self.width * (1 - self.factor))
.offset(x: calcXPosition())
.animation(.easeInOut(duration: 0.2))
HStack {
ForEach(self.elements, id: \.id) { item in
item.view
.gesture(TapGesture().onEnded { _ in
print(item.id)
self.selectedElement = item.id
withAnimation {
self.internalSelectedElement = item.id
}
})
}
}
}
.frame(width: self.width, height: self.height)
.background(self.color)
.cornerRadius(self.cornerRadius)
.padding()
}
}
struct PickerView_Previews: PreviewProvider {
static var previews: some View {
PickerView(.constant(1))
}
}
Change the color where I marked it.
Does anyone know why they behave differently and how I can fix this?
The one line answer is instead of setting backgroundColor, please set contentShape for hit testing.
var body: some View {
GeometryReader { proxy in
self.content()
.fixedSize(horizontal: true, vertical: true)
.frame(minWidth: proxy.size.width, minHeight: proxy.size.height)
// ##################################################################
// CHANGE COLOR HERE TO BLUE TO MAKE IT WORK
// ##################################################################
.contentShape(Rectangle())
// ##################################################################
.border(Color.yellow, width: 5)
}
}
Transparent views are not tappable by default in SwiftUI because their content shape is zero.
You can change this behavior by using .contentShape modifier:
Color.clear
.frame(width: 300, height: 300)
.contentShape(Rectangle())
.onTapGesture { print("tapped") }
It appears to be a design decision that any Color with an opacity of 0 is untappable.
Color.clear.onTapGesture { print("tapped") } // will not print
Color.blue.opacity(0).onTapGesture { print("tapped") } // will not print
Color.blue.onTapGesture { print("tapped") } // will print
Color.blue.opacity(0.0001).onTapGesture { print("tapped") } // will print
You can use the 4th option to get around this, as it is visually indistinguishable from the 1st.
I was struggling a similar problem to get the tap on a RoundedRectangle.
My simple solution was to set the opacity to a very low value and it worked
RoundedRectangle(cornerRadius: 12)
.fill(Color.black)
.opacity(0.0001)
.frame(width: 32, height: 32)
.onTapGesture {
...
}
How do I check to see if dark mode on the device is enabled. I want to check this from within a view and conditionally show or hide a shadow.
I thought I could jus get the colorScheme from the environment but I think I'm missing something.
struct FloatingAddButton : View {
#Environment(\.colorScheme) var colorScheme
#Binding var openAddModal: Bool
var body : some View {
VStack {
Spacer()
HStack() {
Spacer()
Button(action: {
self.openAddModal = true
}) {
ZStack {
Circle()
.foregroundColor(Color(RetroTheme.shared.appMainTint))
.frame(width: 50, height: 50, alignment: .center)
if(self.colorScheme == .light) {
.shadow(color: .secondary, radius: 5, x: 0, y: 0)
}
Image(systemName: "plus")
.foregroundColor(Color.white)
}
} // End Button
}
}
}
}
In my code, I have a simple View extension, that makes the code a lot more readable. With it, I can apply modifiers conditionally:
.conditionalModifier(self.colorScheme == .light, LightShadow())
The full implementation is below:
extension View {
// If condition is met, apply modifier, otherwise, leave the view untouched
public func conditionalModifier<T>(_ condition: Bool, _ modifier: T) -> some View where T: ViewModifier {
Group {
if condition {
self.modifier(modifier)
} else {
self
}
}
}
}
struct FloatingAddButton : View {
#Environment(\.colorScheme) var colorScheme
#Binding var openAddModal: Bool
var body : some View {
VStack {
Spacer()
HStack() {
Spacer()
Button(action: { self.openAddModal = true }) {
ZStack {
Circle()
.foregroundColor(Color(.red))
.frame(width: 50, height: 50, alignment: .center)
.conditionalModifier(self.colorScheme == .light, LightShadow())
Image(systemName: "plus")
.foregroundColor(Color.white)
}
}
} // End Button
}
}
}
struct LightShadow: ViewModifier {
func body(content: Content) -> some View {
content.shadow(color: .secondary, radius: 5, x: 0, y: 0)
}
}
If you ever have a case where you want to apply different modifiers for true and false, here's another extension:
extension View {
// Apply trueModifier if condition is met, or falseModifier if not.
public func conditionalModifier<M1, M2>(_ condition: Bool, _ trueModifier: M1, _ falseModifier: M2) -> some View where M1: ViewModifier, M2: ViewModifier {
Group {
if condition {
self.modifier(trueModifier)
} else {
self.modifier(falseModifier)
}
}
}
}
You are using colorScheme correctly. But it looks like you have a different issue - placing a modifier inside an if statement. I found that, unlike a View, modifiers don't work that way.
The answer is to create a custom ViewModifier. In your case I'd package everything up into one modifier like this:
struct CircleStyle: ViewModifier {
#Environment (\.colorScheme) var colorScheme:ColorScheme
func body(content: Content) -> some View {
if colorScheme == .light {
return content
.foregroundColor(Color(RetroTheme.shared.appMainTint))
.frame(width: 50, height: 50, alignment: .center)
.shadow(color: .secondary, radius: 5, x: 0, y: 0)
} else {
return content
.foregroundColor(Color(RetroTheme.shared.appMainTint))
.frame(width: 50, height: 50, alignment: .center)
}
}
And to use it:
Circle()..modifier(CircleStyle())
If you need to add more variables from your model, simply pass it into your modifier.
Thanks to #dfd for pointing out that I can't use an if statement with a modifier. I updated my code like this for now. This just returns different versions of the circle in light and dark mode.
if colorScheme == .light {
Circle()
.foregroundColor(Color(RetroTheme.shared.appMainTint))
.frame(width: 50, height: 50, alignment: .center)
.shadow(color: .secondary, radius: 5, x: 0, y: 0)
} else {
Circle()
.foregroundColor(Color(RetroTheme.shared.appMainTint))
.frame(width: 50, height: 50, alignment: .center)
}
SwiftUI
With the \.colorScheme key of an Environment variable:
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
var body: some View {
Text(colorScheme == .dark ? "In dark mode" : "In light mode")
}
}
Also, it automatically updates on the change of the environment color scheme.
UIKit
To check the current, all object those conform to UITraitEnvironment protocol, including all UIView subclasses and all UIViewConttroller subclasses have access to the current style:
myUIView.traitCollection.userInterfaceStyle == .dark
myUIViewController.traitCollection.userInterfaceStyle == .dark
To detect the change of the style, here is the full detailed answer
SwiftUI makes it really simply to detect when dark mode is enabled. We simply have to add a #Enviroment variable and use .colorScheme property to scan the settings on our device and see if dark mode is enabled.
Let's take a look at the example below.
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
Color(colorScheme == .light ? .blue : .red)
Text("Hello, World!")
}
}
}
In the code above we are creating the #Environment variable to see if our device is in dark mode. Then inside of our body view we are setting the background color to red if its in dark mode or blue if its not in dark mode by using our colorScheme variable inside of a ternary operator.
A great use case for this is if you want to support different custom UI's for when the users device is in dark mode.
Happy Coding ;