I am trying to create a view like this in SwiftUI (sorry it's so huge):
Specifically, I'm trying to build the scrolling row of labels/bar graph bars in the top quarter of the screen. To me, it looks like a ScrollView with horizontal scrolling, containing some number X of barGraphItems, which are each a VStack with a label and a colored rectangle. The VStack is then rotated 270deg.
Here's the code I have so far:
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
Spacer()
VStack(alignment: .leading, spacing: 0) {
Text("Demo")
.font(.caption)
.fontWeight(.bold)
Rectangle().foregroundColor(.orange)
.frame(width: 84, height: 10)
}
.rotationEffect(Angle(degrees: 270.0))
VStack(alignment: .leading, spacing: 0) {
Text("Demo")
.font(.caption)
.fontWeight(.bold)
Rectangle().foregroundColor(.orange)
.frame(width: 84, height: 10)
}
.rotationEffect(Angle(degrees: 270.0))
VStack(alignment: .leading, spacing: 0) {
Text("Demo")
.font(.caption)
.fontWeight(.bold)
Rectangle().foregroundColor(.orange)
.frame(width: 84, height: 10)
}
.rotationEffect(Angle(degrees: 270.0))
VStack(alignment: .leading, spacing: 0) {
Text("Demo")
.font(.caption)
.fontWeight(.bold)
Rectangle().foregroundColor(.orange)
.frame(width: 84, height: 10)
}
.rotationEffect(Angle(degrees: 270.0))
VStack(alignment: .leading, spacing: 0) {
Text("Demo")
.font(.caption)
.fontWeight(.bold)
Rectangle().foregroundColor(.orange)
.frame(width: 84, height: 10)
}
.rotationEffect(Angle(degrees: 270.0))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here's what it produces (sorry it's so huge):
How can I get the barGraphItems closer together?
How can I add the "Budget" line?
How can I make the whole business scrollable WITHOUT scrolling the Budget line?
I think the other elements onscreen I can suss out, but I've been fiddling with this bar graph widget all afternoon and I'm stumped.
The problem with applying rotation effect to complex container having many views is that layout of container does not change, so all other views affected by rotated container
It looks more appropriate different approach. The idea is to have plot bars as rectangles where height or rectangle show values, and label is a transformed overlay for such rectangle, thus rectangles form layout, and overlays do not affect anything (almost).
Tested with Xcode 11.4 / iOS 13.4
Note: the budget line can be added above plot scrollview by wrap both into ZStack.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
VStack(alignment: .leading, spacing: 0) {
Text("Title above").font(.largeTitle)
ContentView()
Text("comment below")
}
}
}
struct ContentView: View {
var plotHeight: CGFloat = 120
// test with different lenth labels
let demos = ["Demo", "Demoos", "Demoooos", "Demooooooos"]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .bottom, spacing: 24) {
ForEach(0..<20) {_ in
Rectangle().foregroundColor(.orange)
.frame(width: 14, height: CGFloat.random(in: 0..<self.plotHeight)) // values for demo
.overlay(
Text(self.demos.randomElement()!) // values for demo
.font(.caption)
.fontWeight(.bold)
.fixedSize()
.rotationEffect(Angle(degrees: 270.0), anchor: .leading)
.offset(x: -8, y: 6) // align as/if needed here
, alignment: .bottomLeading)
}
}
.frame(height: plotHeight, alignment: .bottom)
.padding(.leading) // required offset for first label in scrollview
}
}
}
To answer your first question: "How can I get the barGraphItems closer together?"
I replaced your code with this:
-These changes fix the problem where your bars begin to be misaligned by adding .fixedSize() to the text.
-You can change the number of bars by increasing or decreasing the range in my for loop. This saves you from copy and pasting the same code.
-You were making several VStacks when you only needed one. This solved the spacing issue and now the bars will come out stuck together with 0 spacing as defined.
Hope this helps!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
VStack(alignment: .leading, spacing: 0) {
ForEach(0..<10) {_ in
Text("Demo")
.font(.caption)
.fontWeight(.bold)
.fixedSize()
Rectangle().foregroundColor(.orange)
.frame(width: 84, height: 10)
}
}
.rotationEffect(Angle(degrees: 270.0))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Related
I'm trying to go to my SwiftUi View File Home by clicking my button in iOS 16:
I already read Apple documentation and searched in Google and YouTube but I didn't got the answer.
Here is my code:
import SwiftUI
import CoreData
struct ContentView: View {
var body: some View {
VStack (alignment: .leading) {
Text("Welcome To").font(.system(size: 45)).fontWeight(.heavy).foregroundColor(.primary)
Text("Pumping Fitness").font(.system(size: 45)).fontWeight(.heavy).gradientForeground(colors: [.red, .yellow])
Spacer()
VStack (alignment: .leading, spacing: 24) {
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "dumbbell.fill").resizable().frame(width: 40, height: 30).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Track your workouts").bold().font(.system(size: 22)).padding(.top, 10.0)
Text("Easily track your progress during you are working out").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "timer").resizable().frame(width: 40, height: 40).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Auto rest timer").bold().font(.system(size: 22))
Text("Start your rest time with one single tap").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "figure.run").resizable().frame(width: 40, height: 50).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Add your own exercises").bold().font(.system(size: 22))
Text("Create your own type of exercises at a glance").font(.subheadline)
}
}
}
Spacer()
Spacer()
//HStack creado para poder alinear el boton al centro.
HStack(alignment: .center) {
Button(action: {} ) {
Text("Start Pumping").fontWeight(.black).foregroundColor(.white)
}
.padding()
.frame(width: 280, height: 60)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.yellow]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(17)
}.padding(.leading)
}.padding(.all, 40)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
extension View {
public func gradientForeground(colors: [Color]) -> some View {
self.overlay(LinearGradient(gradient: .init(colors: colors), startPoint: .topLeading, endPoint: .topTrailing))
.mask(self)
}
}
Do you know how can I do it? All the YouTube videos I saw were using a list, and I want to show this "welcome page" then go to my home page.
You need to refactor your code a bit. Use ContentView as your module for navigationStack. Separate ContentView code to WelcomeView and use it as Follows:
struct WelcomeView: View {
#Binding var gotoSomewhere: Bool // I recommend you to read some articles about #Sate and #Binding property wrappers
var body: some View {
VStack (alignment: .leading) {
Text("Welcome To").font(.system(size: 45)).fontWeight(.heavy).foregroundColor(.primary)
Text("Pumping Fitness").font(.system(size: 45)).fontWeight(.heavy).gradientForeground(colors: [.red, .yellow])
Spacer()
VStack (alignment: .leading, spacing: 24) {
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "dumbbell.fill").resizable().frame(width: 40, height: 30).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Track your workouts").bold().font(.system(size: 22)).padding(.top, 10.0)
Text("Easily track your progress during you are working out").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "timer").resizable().frame(width: 40, height: 40).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Auto rest timer").bold().font(.system(size: 22))
Text("Start your rest time with one single tap").font(.subheadline).padding(.bottom, 10.0)
}
}
HStack (alignment: .center, spacing: 20)
{
Image(systemName: "figure.run").resizable().frame(width: 40, height: 50).gradientForeground(colors: [.red, .orange])
VStack (alignment: .leading, spacing: 4) {
Text("Add your own exercises").bold().font(.system(size: 22))
Text("Create your own type of exercises at a glance").font(.subheadline)
}
}
}
Spacer()
Spacer()
//HStack creado para poder alinear el boton al centro.
HStack(alignment: .center) {
Button(action: {
print("Tap Tap")
gotoSomewhere = true
} ) {
Text("Start Pumping").fontWeight(.black).foregroundColor(.white)
}
.padding()
.frame(width: 280, height: 60)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.yellow]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(17)
}.padding(.leading)
}.padding(.all, 40)
}
}
Then update your contentView with NavigationStack like this:
import SwiftUI
import CoreData
struct ContentView: View {
#State var gotoHomePage: Bool = false // this is important
var body: some View {
NavigationStack {
VStack {
WelcomeView(gotoSomewhere: $gotoHomePage)
NavigationLink(isActive: $gotoHomePage) {
HomeView() // This is your Home View you want to navigate to
.navigationBarBackButtonHidden(true) // if you want a back button then pass false or comment this modifier
} label: {
EmptyView()
}
}
.navigationBarHidden(true)
}
}
}
Hope this helps.
If you're looking to use a navigation stack, then you'd want to wrap your View body inside a NavigationView. If you're targeting iOS 16+ you should use NavigationStack instead (https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types). Hacking with Swift also has an article on NavigationStack.
There is some more useful info on NavigationView on the Hacking with Swift blog here, as well as other resources you should be able to find online. There are some examples there you could use in your situations such as:
struct ContentView: View {
#State private var isShowingDetailView = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Second View"), isActive: $isShowingDetailView) { EmptyView() }
Button("Tap to show detail") {
self.isShowingDetailView = true
}
}
.navigationTitle("Navigation")
}
}
}
This question is pretty similar: How to show NavigationLink as a button in SwiftUI
I'm trying to create a chat bubble like this:
Actual Bubble
Actual Bubble 2.0
This is what I have been able to achieve so far.
My attempt
My attempt
This is my code so far:
import SwiftUI
struct TestingView: View {
var body: some View {
ZStack {
/// header
VStack(alignment: .trailing) {
HStack {
HStack() {
Text("abcd")
}
HStack {
Text("~abcd")
}
}.padding([.trailing, .leading], 15)
.fixedSize(horizontal: false, vertical: true)
/// text
HStack {
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
}.padding([.leading, .trailing], 15)
/// timestamp
HStack(alignment: .center) {
Text("12:00 PM")
}.padding(.trailing,15)
}.background(Color.gray)
.padding(.leading, 15)
.frame(maxWidth: 250, alignment: .leading)
}
}
}
struct TestingView_Previews: PreviewProvider {
static var previews: some View {
TestingView()
}
}
The main goal is that I want the two labels on top to be distant relative to the size of the message content. I am not able to separate the two labels far apart i.e one should be on the leading edge of the bubble and the other one on the trailing edge.
Already tried spacer, it pushes them to the very edge, we need to apart them relative to the content size of the message as shown in attached images.
Here is a simplified code.
Regarding Spacer: To achieve your desired result you put both Text views inside of a HStack, and put a Spacer between them. So the Spacer pushes them apart to the leading and trailing edge.
Also I recommend to only use one padding on the surrounding stack.
VStack(alignment: .leading) {
// header
HStack {
Text("+123456")
.bold()
Spacer() // Spacer here!
Text("~abcd")
}
.foregroundStyle(.secondary)
// text
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
.padding(.vertical, 5)
// timestamp
Text("12:00 PM")
.frame(maxWidth: .infinity, alignment: .trailing)
}
.padding()
.background(Color.gray.opacity(0.5))
.cornerRadius(16)
.frame(maxWidth: 250, alignment: .leading)
}
We can put that header into overlay of main text, so it will be always aligned by size of related view, and then it is safe to add spacer, `cause it do not push label wider than main text.
Tested with Xcode 13.4 / iOS 15.5
var body: some View {
let padding: CGFloat = 15
ZStack {
/// header
VStack(alignment: .trailing) {
/// text
HStack {
//Text("Hello Everyone") // short test
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!") // long test
}
.padding(.top, padding * 2)
.overlay(
HStack { // << here !!
HStack() {
Text("abcd")
}
Spacer()
HStack {
Text("~abcd")
}
}
, alignment: .top)
.padding([.trailing, .leading], padding)
/// timestamp
HStack(alignment: .center) {
Text("12:00 PM")
}.padding(.trailing, padding)
}.background(Color.gray)
.padding(.leading, padding)
.frame(maxWidth: 250, alignment: .leading)
}
}
To separate two components with fairly space in the middle, use HStack{} with Spacer().
This is a sample approach for this case. Code is below the image:
VStack {
HStack {
Text("+92 301 8226")
.foregroundColor(.red)
Spacer()
Text("~Usman")
.foregroundColor(.gray)
}
.padding(.bottom, 5)
.padding(.horizontal, 5)
Text("Testing testingtesting testing testing testingtesting testing testing testing testing testing testing testing testing testing.")
.padding(.horizontal, 5)
HStack {
Spacer()
Text("2:57 AM")
.foregroundColor(.gray)
.font(.subheadline)
}
.padding(.trailing, 5)
}
.frame(width: 300, height: 160)
.background(.white)
.cornerRadius(15)
I am trying to make an adaptive CommentViewRow for a social app where the whole comment text can be displayed within the row.
So far I am achieving this but I have 3 issues:
1 When the comment-text is short it is being centred within the row even when I specify ".alignment: .leading" in the VStack.
2 When the comment-text uses more than one line there is a mysterious padding between the user's profile picture & the comment-text?? see image below.
3 I am not sure if my .frame modifier is the best way to achieve what I am doing, it seems mickey-mouse, I was reading about .frame(idealWith, idealHeight, etc..) and not sure if that would help.
Any idea on how I can fix this so that each CommentViewRow displays like your average social-media comment view??
Thank you!
struct CommentViewRow: View {
var comment: Comment
var body: some View {
HStack {
KFImage("profilePicture")
// COMMENT
VStack(alignment: .leading, spacing: 5) {
Text("**\(comment.username)** \(comment.comment)")
.font(.caption)
.frame(width: 310)
.fixedSize(horizontal: true, vertical: false)
Text(comment.createdAt.timeAgoDisplay())
.bold()
.font(.caption)
}
Spacer()
}.padding([.leading, .trailing], 10)
}
}
1st option: If you really need that view to be 310 wide
You can change .frame(width: 310) to .frame(width: 310, alignment: .leading)
2nd option: Let the view adjust itself based on content, you just need to specify the alignment (.leading in this case)
struct CommentViewRow: View {
var comment: Comment
var body: some View {
HStack {
KFImage("profilePicture")
VStack(alignment: .leading, spacing: 5) {
Text("**\(comment.username)** \(comment.comment)")
.font(.caption)
Text(comment.createdAt.timeAgoDisplay())
.font(.caption.bold())
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding([.horizontal], 10)
}
}
yes, get rid of the frame:
struct ContentView: View {
var comment = "Thjfhg jhfgjhdfg jdfhgj dfhdfsjjdfgh djdshfg hjdfgjfdh ghjkf gdhjdfgh jkh fjg dfjkhgj dfglkhdfsg"
var body: some View {
HStack {
Image(systemName: "person.circle")
.font(.largeTitle)
VStack(alignment: .leading, spacing: 5) {
Text("\(comment)")
.font(.caption)
Text("3 minutes ago")
.bold()
.font(.caption)
}
Spacer()
}.padding([.leading, .trailing], 10)
}
}
I am trying to create an navigation view that works on both iPhone and iPad. Currently I have it working on the iPhone however when running it on the iPad the navigation view doesnt present my main view properly. See below:
This is when I load the app
If I press products (top left) it opens up the products tab.
When I click on a product it goes to this screen
If I click Product 1 (seen on 3rd image) It opens all the details into another navigation bar.
What I am trying to achieve is that image 4 isn't in a navigation tab and instead it is the full screen. I tried removing NavigationView from my code which seems to fix the problem and makes it full screen. However, I then lose the navigation view buttons to allow the user to view other products.
Here is a shortened version my code (without all the text/image details):
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .center, spacing: 20) {
ProductHeaderView(product: product)
VStack(alignment: .leading, spacing: 15) {
Text(product.title)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(product.gradientColors[1])
Text(product.headline)
.font(.headline)
.multilineTextAlignment(.leading)
}
.padding(.horizontal, 20)
.frame(maxWidth: 640, alignment: .center)
}
.navigationBarTitle(product.title, displayMode: .inline)
.navigationBarHidden(true)
}
.edgesIgnoringSafeArea(.top)
}
}
}
Thank you in advance for your help :)
EDIT:
Here is the ProductHeaderView.swift code:
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: product.gradientColors), startPoint: .topLeading, endPoint: .bottomTrailing)
TabView{
ForEach(0..<product.images.count, id: \.self) { item in
Image(product.images[item])
.resizable()
.scaledToFit()
.shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.15), radius: 8, x: 6, y: 8)
.scaleEffect(isAnimatingImage ? 1.0 : 0.6)
}//: FOR LOOP
}//: TAB VIEW
.tabViewStyle(PageTabViewStyle())
.padding(.vertical, 0)
} //: ZSTACK
.frame(height: 414)
.onAppear(){
withAnimation(.easeOut(duration: 0.5)){
isAnimatingImage = true
}
}
}
Example project: https://github.com/spoax94/productsMinimal.git
Just add this line as a modifier in your NavigationView:
.navigationViewStyle(StackNavigationViewStyle())
As I commented there should be only one NavigationView, so here fixed ProductDetailView with removed redundant NavigationView.
Tested with Xcode 12
struct ProductDetailView: View {
var product: Product
var products: [Product] = productData
#State var showingPreview = false
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .center, spacing: 20) {
ProductHeaderView(product: product)
VStack(alignment: .leading, spacing: 15) {
Text(product.title)
.font(.largeTitle)
.fontWeight(.heavy)
Text(product.headline)
.font(.headline)
.multilineTextAlignment(.leading)
Text("Learn More About \(product.title)".uppercased())
.fontWeight(.bold)
.padding(0)
Text(product.description)
.multilineTextAlignment(.leading)
.padding(.bottom, 10)
}
.padding(.horizontal, 20)
.frame(maxWidth: 640, alignment: .center)
}
.navigationBarTitle(product.title, displayMode: .inline)
.navigationBarHidden(true)
}
.edgesIgnoringSafeArea(.top)
}
}
I figured out the problem. I removed the navigationView and also the 2 lines
.navigationBarTitle(product.title, displayMode: .inline)
.navigationBarHidden(true)
As this was hiding the navigation buttons at the top of my view.
I am trying to create a view in SwiftUI where the background of the image on the left should scale vertically based on the height of the text on the right.
I tried a lot of different approaches, from GeometryReader to .layoutPriority(), but I haven't managed to get any of them to work.
Current state:
Desired state:
I know that I could imitate the functionality by hardcoding the .frame(100) for the example I posted, but as text on the right is dynamic, that wouldn't work.
This is full code for the view in the screenshot:
import SwiftUI
struct DynamicallyScalingView: View {
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.font(.system(size: 32))
.padding(20)
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
}
.padding(.horizontal)
}
}
struct DailyFactView_Previews: PreviewProvider {
static var previews: some View {
DynamicallyScalingView()
}
}
Here is a solution based on view preference key. Tested with Xcode 11.4 / iOS 13.4
struct DynamicallyScalingView: View {
#State private var labelHeight = CGFloat.zero // << here !!
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.font(.system(size: 32))
.padding(20)
.frame(minHeight: labelHeight) // << here !!
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
.background(GeometryReader { // << set right side height
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
}
.onPreferenceChange(ViewHeightKey.self) { // << read right side height
self.labelHeight = $0 // << here !!
}
.padding(.horizontal)
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
This is the answer without workaround.
struct DynamicallyScalingView: View {
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.frame(maxHeight: .infinity) // Add this
.font(.system(size: 32))
.padding(20)
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
.frame(maxHeight: .infinity) // Add this
}
.padding(.horizontal)
.fixedSize(horizontal: false, vertical: true) // Add this
}
}