How to build a horizontal chart in SwiftUI - swift

I would like to build the following view and be able to control how much each view fills the outer rectangle:
This is what I have so far:
import SwiftUI
struct TestView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle().fill(Color.blue)
Rectangle()
.fill(Color.yellow)
Rectangle()
.fill(Color.pink)
}
.cornerRadius(8)
.frame(maxHeight: 25)
}
}
Since a HStack decides how to lay out the views on its own, I'm not sure if that's the right View to use. I've tried with a Capsule but it's just a rounded rectangle. What can I use to build the View above? Ideally, I would like to give a percentage to the view and then make it fill appropriately.

Here is a demo of possible approach (tested with Xcode 12 / iOS 14)
var body: some View {
Color.clear // << placeholder
.frame(maxWidth: .infinity, maxHeight: 25)
.overlay(GeometryReader { gp in
// chart is here
HStack(spacing: 0) {
Rectangle().fill(Color.blue)
.frame(width: 0.6 * gp.size.width)
Rectangle()
.fill(Color.yellow)
.frame(width: 0.1 * gp.size.width)
Rectangle()
.fill(Color.pink)
.frame(width: 0.3 * gp.size.width)
}
})
.clipShape(RoundedRectangle(cornerRadius: 8))
}

Related

SwiftUI, add inverted Opacity

Please see the attach image below, I want to the make this view in siwftUI that have zero opacity inside.
struct TestView: View {
var body: some View {
ZStack{
// any dummy image
Image("bigpancake")
.resizable()
ZStack {
// Make this view opcity zero so that I can see backgourd clearly
Rectangle()
.frame(width: 200, height: 100)
.foregroundColor(.black.opacity(0))
Rectangle()
.foregroundColor(.black.opacity(0.3))
}
}
}
}
If I understand correctly what you are looking for, it sounds like an "inverted mask". You can achieve what you want using compositingGroup()
Example:
struct ContentView: View {
var body: some View {
ZStack {
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
ZStack {
Rectangle()
.foregroundColor(.black.opacity(0.3))
.ignoresSafeArea()
Rectangle()
.cornerRadius(20)
.frame(width: 200, height: 200)
.blendMode(.destinationOut)
.overlay {
Text("Content")
}
}.compositingGroup()
}
}
}

Unexpected layout issues with SwiftUI Image and ZStack

I'm trying to create a view composed of two parts - the top part is an image with a piece of text overlaid in the top left corner. The top part should take up 2/3 of the height of the view. The bottom part is text information and a button, contained in an HStack, and takes up the remaining 1/3 of the view height.
struct MainView: View {
var body: some View {
GeometryReader { gr in
VStack(spacing: 0) {
ImageInfoView()
.frame(height: gr.size.height * 0.66)
BottomInfoView()
}
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}
}
struct ImageInfoView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Image("testImage")
.resizable()
.aspectRatio(contentMode: .fill)
.overlay(Rectangle()
.fill(Color.black.opacity(0.5))
)
HStack {
Text("Top left text")
.foregroundColor(.white)
.padding()
}
}
}
}
struct BottomInfoView: View {
var body: some View {
HStack {
VStack(alignment:.leading) {
Text("Some title")
Text("Some subtitle")
}
Spacer()
Button("BUTTON") {
}
}
.padding()
.background(.gray)
}
}
In the below sample code, if I set the frame height to 400 I will see the top left text, but when I set it to 200, I do not. What is happening here? The text should be anchored to the top left corner of ImageInfoView no matter what. Further, when set to 400, why does the ImageInfoView take up more than 66% of the height?
struct TestView: View {
var body: some View {
MainView()
.frame(height: 200)
.padding([.leading, .trailing], 16)
}
}
An image's fill pushes ZStack boundaries, so Text goes off screen (see this for details).
A possible solution is to put image into overlay of other container so it respects provided space instead of imposing own.
Tested with Xcode 13.4 / iOS 15.5
ZStack(alignment: .topLeading) {
Color.clear.overlay( // << here !!
Image("testImage")
.resizable()
.aspectRatio(contentMode: .fill)
.overlay(Rectangle()
.fill(Color.black.opacity(0.5))
)
)
// ... other code as-is
This fixes the problem:
ZStack(alignment: .topLeading) {
GeometryReader { gr in
Image("testImage")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: gr.size.width, maxHeight: gr.size.height)
.overlay(Rectangle()
.fill(Color.black.opacity(0.5))
)
HStack {
Text("Top left text")
.foregroundColor(.white)
.padding()
}
}
}
}

Adjusting big or small screens using geometry reader

I am currently stuck in trouble i am not really able to resize image and texts to fit bigger or small screens. This is just a simple card that i want to be able to be resizable and should be in the same proportions. I have even tried geometry reader. I am having trouble as even if I have used geometry reader reader the white spaces I wanted are not there as the picture takes the entire width and height which looks bad not ideal. If there is a way for me to have the same proportion of the card for all different screen sizes then I am all open to ideas.
Image of my card:
Here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Image("shops")
.resizable()
.aspectRatio(contentMode: .fill)
VStack {
Spacer()
Text("Welcome to MarketInfo")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
}
.padding(.bottom, 30)
.frame(width: 400)
}
.frame(width: 380, height: 270)
.cornerRadius(20)
.clipped()
.shadow(radius: 8)
.padding(.top, 20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
ContentView()
}
}
}
I think where you are running into issues is in using the fixed frame. The issue with using it is that it does not adjust for sizes. If you had something complicated, you could use a GeometryReader to resize the frame based on a proportion of the available space. However, the simplest implementation is setting your Text as an .overlay() on your image and setting the .aspectRatio(contentMode: .fit).
The code would now be:
struct ContentView: View {
var body: some View {
Image("shops")
.resizable()
.aspectRatio(contentMode: .fit)
.overlay(
VStack {
Spacer()
Text("Welcome to MarketInfo")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
}
)
.cornerRadius(20)
.clipped()
.shadow(radius: 8)
.padding(30)
}
}
The overlay is never bigger than the Image, which can be an issue using a ZStack, and it will move with the Image. I then just through some padding on the Image all around to keep it in from the sides, and away from other views.
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack() {
Image("shops")
.resizable()
.aspectRatio(1.4, contentMode: .fit) // 380 / 270 = 1.4
.overlay(
VStack {
Spacer()
Text("Welcome to MarketInfo")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
.lineLimit(1)
.minimumScaleFactor(0.5)
.padding()
})
}
// .frame is not required, but Change .infinity to a number will resize the card
.frame(maxWidth: .infinity)
.cornerRadius(20)
.shadow(radius: 8)
.padding(.horizontal, 20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 13 Pro")
ContentView()
.previewDevice("iPod touch (7th generation)")
ContentView()
.previewDevice("iPad Pro (12.9-inch) (5th generation)")
}
}

How to make a Rectangle() and a DatePicker overlap?

So I'm trying to move the rectangle so that it looks like there's a little point at the bottom of the DatePicker() UI element:
import SwiftUI
struct TimeSelectorView: View {
#State private var selectedDay: Date = Date()
var body: some View {
VStack {
DatePicker(selection: $selectedDay, in: Date()..., displayedComponents: .date) {
Text("")
}.frame(width: UIScreen.main.bounds.width * 0.75)
.clipped()
.background(Color.white)
.cornerRadius(15)
Rectangle()
.fill(Color.white)
.frame(width: 25, height: 25)
.rotationEffect(Angle(degrees: 45))
.padding(.bottom, 15)
}
}
}
To create overlapping Views you can use a ZStack.
SwiftUI has a dedicated stack type for creating overlapping content,
which is useful if you want to place some text over a picture for
example. It’s called ZStack, and it works identically to the other two
stack types [HStack, VStack].
In the below example, the DatePicker will be placed behind the Rectangle:
ZStack {
DatePicker(...)
Rectangle()
}
You can find more information in this tutorial: How to layer views on top of each other using ZStack?

Is it possible to get the calculated "minimum" height for a VStack in Swift UI

I'm working with a card-like view, and I would essentialy like to know the minimum height needed to house the elements, and then add a border around that.
this becomes difficult since VStack and HStack seem to want to take up the most space possible, so I need to manually insert a .frame(...) to coercse this to work, but that makes it very unflexible.
In the picture I'm highlighting the VStack which, as you can see, is overfilling beyond the constraints of the 48px frame height.
code
var body: some View {
HStack(spacing: 0) {
Image("cocktail")
.resizable()
.scaledToFit()
.padding(Size.Spacing.unit * 2)
.background(self.color)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(self.color)
)
HStack(spacing: 0) {
VStack(alignment: .leading) {
Text("Negroni").font(.headline)
Spacer()
HStack {
Tag(name: "bitter")
Tag(name: "sweet")
Tag(name: "strong")
}
}
.padding(.leading, 10)
Spacer()
}
}
.padding(Size.Spacing.unit * 2)
.frame(height: 48.0)
}
maybe i misunderstand you, but i tried your example...unfortunately it was not compilable, so i had to change some things. but as you can see the result (vstack - yellow) is not taking to much space, maybe because of your padding?
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Image("cocktail")
.resizable()
.scaledToFit()
// .padding(Size.Spacing.unit * 2)
.background(Color.blue)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.yellow)
)
HStack(spacing: 0) {
VStack(alignment: .leading) {
Text("Negroni").font(.headline)
Spacer()
HStack {
Text("bitter")
Text("sweet")
Text("strong")
}
}
.background(Color.yellow)
.padding(.leading, 10)
Spacer()
}.background(Color.red)
}
// .padding(Size.Spacing.unit * 2)
.frame(height: 48.0)
}
}
since VStack and HStack seem to want to take up the most space possible
This assumption is incorrect - by default stacks are tight to content and content tight to minimum with default padding between UI elements. In provided code it is Spacer() responsibility on expanding to max available space. So to make minimum space for content, highlighted VStack should be changed as follows:
VStack(spacing: 0, alignment: .leading) {
Text("Negroni").font(.headline)
HStack {
Tag(name: "bitter")
Tag(name: "sweet")
Tag(name: "strong")
}
}