SwiftUI Charts extension Image: Plottable - swiftui

Is there any sensible way to create sth like extension Image: Plottable and pass to .value inside BarMark. Or there is any other possibility to have images below charts instead text?
I try sth like but it of course doesn't work
extension Image: Plottable {
public var primitivePlottable: String {
????
}
public init?(primitivePlottable: String) {
self.init(primitivePlottable)
}
}

ok i found a slightly different solution to this problem using annotation modifier with position: .bottom like:
BarMark(x: .value("Shape Type", shape.type),
y: .value("Total Count", shape.count))
.annotation(position: .bottom, alignment: .center, spacing: 10, content: {
Image(shape.type)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
})

Related

SwiftUI overlay deprecated alternatives

I'm trying to follow this calculator tutorial but am running into several issues. One of the issues is the use of the .overlay method. I'm assuming it doesn't work because it is deprecated, but I can't figure out how to get the recommeded way or get anything else to work to solve it, so am reaching out for options.
Xcode 12.4
Target: iOS 14.4
Here is the code in that section:
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(foregroundColor)
/* //Commented out to compile
.overlay( {
if configuration.isPressed {
Color(white: 1.0, opacity: 0.2)
}
)
}
*/
//.clipShape(Capsule()) //this makes circle buttons
} //func
} //struct
I've tried commenting out that section which is the only way to have it compile, but then the button press action of showing a different color does not work.
Overlay is not deprecated. Where did you read that. I think your problem is that you try to use the overlay function in a button style, which is not possible. You can only use it in a view. The error you wrote behind it, also states that.
I'm also not sure what you want to achieve, because doesn't it work correct if you just not use the overlay?
I get this button without the overlay. Is that what you need?
With the button style:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button("1") {
print("being pressed")
}
.buttonStyle(
CalculatorButtonStyle(
size: 40,
backgroundColor: .cyan,
foregroundColor: .black
)
)
}
.padding()
}
}
I also added the changed button style.
import SwiftUI
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(configuration.isPressed ? Color(white: 1.0, opacity: 0.2) : foregroundColor)
.clipShape(Capsule()) //this makes circle buttons
}
}
An overlay is not needed. Take advantage of the isPressed value of the configuration to change the color
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(configuration.isPressed
? Color(white: 1.0, opacity: 0.5)
: foregroundColor)
.clipShape(Capsule())
} //func
} //struct
You have two stray parentheses:
// Remove this
// ↓
.overlay( {
if configuration.isPressed {
Color(white: 1.0, opacity: 0.2)
}
) // ← Remove this
}
The stray left parenthesis (the one immediately after .overlay) prevents Swift from recognizing the trailing closure syntax and makes Swift think you are trying to use the deprecated version of overlay.
The stray right parenthesis is improperly nested relative to the right-brace on the following line.
If you copy and paste the code from the tutorial, or if you remove those two parentheses, the code compiles:
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(foregroundColor)
.overlay {
if configuration.isPressed {
Color(white: 1.0, opacity: 0.2)
}
}
.clipShape(Capsule())
}
}

In SwiftUI, how to fill Path with text color on top of a material?

I'm drawing icons on a toolbar with a material background. The Text and symbol Images are white, but if I draw my own Path, it's gray.
struct ContentView: View {
var body: some View {
HStack {
Text("Hi")
Image(systemName: "square.and.arrow.up.fill")
Path { p in
p.addRect(CGRect(origin: .zero, size: .init(width: 20, height: 30)))
}.fill()
.frame(width: 20, height: 30)
}
.padding()
.background(.regularMaterial)
}
}
I get the same result with .fill(), .fill(.foreground), or .fill(.primary).
Why is it gray? How do I get it to match the white text color?
I find it weird that .white or .black work, but .primary doesn't.
Upon discovering the Material documentation, I found this interesting snippet:
When you add a material, foreground elements exhibit vibrancy, a context-specific blend of the foreground and background colors that improves contrast. However using foregroundStyle(_:) to set a custom foreground style — excluding the hierarchical styles, like secondary — disables vibrancy.
Seems like you have to force a different color (see previous edit which I used the environment color scheme), since hierarchical styles such as .primary won't work by design.
Luckily there is a way around this - you can use colorMultiply to fix this problem. If you set the rectangle to be .white, then the color multiply will make it the .primary color.
Example:
struct ContentView: View {
var body: some View {
HStack {
Text("Hi")
Image(systemName: "square.and.arrow.up.fill")
Path { p in
p.addRect(CGRect(origin: .zero, size: .init(width: 20, height: 30)))
}
.foregroundColor(.white)
.colorMultiply(.primary)
.frame(width: 20, height: 30)
}
.padding()
.background(.regularMaterial)
}
}
There is no issue with code, your usage or expecting is not correct! Text and Image in that code has default Color.primary with zero code! So this is you, that messing with .fill() you can delete that one!
struct ContentView: View {
var body: some View {
HStack {
Text("Hi")
Image(systemName: "square.and.arrow.up.fill")
Path { p in
p.addRect(CGRect(origin: .zero, size: .init(width: 20, height: 30)))
}
.fill(Color.primary) // You can delete this line of code as well! No issue!
.frame(width: 20, height: 30)
}
.padding()
.background(Color.secondary.cornerRadius(5))
}
}

How to properly present PKPaymentAuthorizationViewController in a SwiftUI only project?

I have visited this answer already and the present method proposed here doesn't work (anymore?). When I use a UIViewControllerRepresentable and then show it as a sheet it looks pretty awful:
If I use overlay it looks exactly like I want it to looks but overlay cannot be triggered from a Button.
Here is the (condensed) code:
public struct ContentView: View {
#ObservedObject var model: RootViewModel
#State private var tappedDonate = false
public var body: some View {
Button(action: {
tappedDonate = true
}, label: {
Text("Donate")
.frame(width: 300, height: 44, alignment: .center)
})
.frame(width: 300, height: 20, alignment: .center)
.padding()
.background(Color.black)
.foregroundColor(.white)
.cornerRadius(22)
.sheet(isPresented: $tappedDonate) {
ApplePayWrapper(request: model.buildApplePayment())
.background(Color.clear)
}
}
public init(model: RootViewModel) {
self.model = model
}
}
I'm not 100% convinced this is the best way to do it, but I managed to make it work visually like I intended it to work using a ZStack:
ZStack {
Button(action: {
tappedDonate = true
}, label: {
Text("Donate")
.frame(width: 300, height: 44, alignment: .center)
})
.frame(width: 300, height: 20, alignment: .center)
.padding()
.background(Color.black)
.foregroundColor(.white)
.cornerRadius(22)
if tappedDonate {
ApplePayWrapper(request: model.buildApplePayment())
}
}
Note that you still need to hook up the cancel button as well as the regular dismiss by tapping outside through the delegate, otherwise the underlying UI with the button does not respond to touch again.

List view - any way to scroll horizontally?

I have a list that loads from a parsed CSV file built using SwiftUI and I can't seem to find a way to scroll the list horizontally.
List {
// Read each row of the array and return it as arrayRow
ForEach(arrayToUpload, id: \.self) { arrayRow in
HStack {
// Read each column of the array (Requires the count of the number of columns from the parsed CSV file - itemsInArray)
ForEach(0..<self.itemsInArray) { itemNumber in
Text(arrayRow[itemNumber])
.fixedSize()
.frame(width: 100, alignment: .leading)
}
}
}
}
.frame(minWidth: 1125, maxWidth: 1125, minHeight: 300, maxHeight: 300)
.border(Color.black)
The list renders how I would like but I'm just stuck on this one point.
Preview Image Of Layout
Swift 5;
iOS 13.4
You should use an ScrollView as Vyacheslav Pukhanov suggested but in your case the scrollView size does not get updated after the async call data arrive. So you have 2 options:
Provide a default value or an alternative view.
Provide a fixed size to the HStack inside of the ForeEach. (I used this one)
I faced the same problem laying out an horizontal grid of two columns. Here's my solution
import SwiftUI
struct ReviewGrid: View {
#ObservedObject private var reviewListViewModel: ReviewListViewModel
init(movieId: Int) {
reviewListViewModel = ReviewListViewModel(movieId: movieId)
//ReviewListViewModel will request all reviews for the given movie id
}
var body: some View {
let chunkedReviews = reviewListViewModel.reviews.chunked(into: 2)
// After the API call arrive chunkedReviews will get somethig like this => [[review1, review2],[review3, review4],[review5, review6],[review7, review8],[review9]]
return ScrollView (.horizontal) {
HStack {
ForEach(0..<chunkedReviews.count, id: \.self) { index in
VStack {
ForEach(chunkedReviews[index], id: \.id) { review in
Text("*\(review.body)*").padding().font(.title)
}
}
}
}
.frame(height: 200, alignment: .center)
.background(Color.red)
}
}
}
This is a dummy example don't expect a fancy view ;)
I hope it helps you.
You should use a horizontal ScrollView instead of the List for this purpose.
ScrollView(.horizontal) {
VStack {
ForEach(arrayToUpload, id: \.self) { arrayRow in
HStack {
ForEach(0..<self.itemsInArray) { itemNumber in
Text(arrayRow[itemNumber])
.fixedSize()
.frame(width: 100, alignment: .leading)
}
}
}
}
}

SwiftUI - How do I check to see if dark mode is enabled?

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 ;