Swiftui tabview layout issue - swiftui

I have tried to have a layout like the following screen shots. Basically a long vertical scrolling view, with a number of round corner panes.
My code as follows:
struct DetailView: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
ZStack {
Image("Dummy")
.resizable()
.aspectRatio(contentMode: .fill)
.opacity(0.2)
.frame(width: .infinity, height: .infinity)
.edgesIgnoringSafeArea(.all)
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
TabView {
VStack(alignment: .leading, spacing:0) {
HStack {
Text("A long Text title").font(.title)
Spacer()
}.frame(width:UIScreen.main.bounds.size.width*0.9)
HStack {
Text("A long Text title").font(.title)
Spacer()
}.frame(width:UIScreen.main.bounds.size.width*0.9)
HStack {
Text("A long Text title").font(.title)
Spacer()
}.frame(width:UIScreen.main.bounds.size.width*0.9)
HStack {
Text("A long Text title").font(.title)
Spacer()
}.frame(width:UIScreen.main.bounds.size.width*0.9)
}
}.tabViewStyle(PageTabViewStyle())
.background(Color.green)
TabView {
ImageView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(UIConstants.cardCornerRadius)
ImageView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(UIConstants.cardCornerRadius)
ImageView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(UIConstants.cardCornerRadius)
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.background(Color.red)
TabView {
TextInfoView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(UIConstants.cardCornerRadius)
.padding([.horizontal])
}.tabViewStyle(PageTabViewStyle())
.background(Color.green)
TabView {
TextInfoView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(UIConstants.cardCornerRadius)
.padding([.horizontal])
}.tabViewStyle(PageTabViewStyle())
.background(Color.purple)
}
}
}
}
}
struct TextInfoView: View {
var body: some View {
VStack {
Text("Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview. Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.").padding()
}
.background(Color.yellow)
}
}
struct ImageView: View {
var body: some View {
Image("Dummy")
.resizable()
.aspectRatio(contentMode: /*#START_MENU_TOKEN#*/.fill/*#END_MENU_TOKEN#*/)
.frame(height:UIScreen.main.bounds.size.height*0.7)
}
}
The results as follows:
A few issues here:
You can see that I tried to wrap the TextInfoView with a TabView as well. It is because if I do not enclose it in a TabView, it cannot align with the TabView with ImageView above. Any method to solve this issue?
At the top, the green area. Why there is such large padding around the text? How can I remove those extra green area? I want it to be tight at top and bottom.
If I do not add .frame() for the green area text, I found that the text gone, and cannot align to the left. Any method to solve this?

struct DetailViews: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
VStack{
Text("A long Text Title")
Text("A long Text Title")
Text("A long Text Title")
Text("A long Text Title")
}
.frame(maxWidth: .infinity)
.background(Color.yellow)
TabView {
ImageView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(15)
ImageView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(15)
ImageView()
.frame(width:UIScreen.main.bounds.size.width*0.9)
.cornerRadius(15)
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.background(Color.red)
TextInfoView()
.cornerRadius(15)
.padding()
.background(Color.blue)
TextInfoView()
.cornerRadius(15)
.padding()
.background(Color.purple)
}
}
}
}
}
struct TextInfoView: View {
var body: some View {
VStack {
Text("Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview. Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview.")
.fixedSize(horizontal: false, vertical: true).padding()//added
}
.background(Color.yellow)
}
}

Related

gutters on sides of List in WatchOS

I'm dipping my toe into SwiftUI and WatchOS for the first time. I'm making good progress, but I can't figure out how to get rid of the black "gutters" on either side of my Image controls. I've tried setting all the backgrounds to white, but the gutter persists.
What property on which view do I need to set to change the color of the gutters to match the background?
SwiftUI
struct ContentView: View {
var body: some View {
List {
Image("cat-1").resizable().scaledToFill().background(Color.white)
Image("cat-2").resizable().scaledToFit().padding(5).background(Color.white)
Image("cat-3").resizable().scaledToFit().padding(.top, 5).background(Color.white)
}.background(Color.white).listStyle(CarouselListStyle())
.background(Color.white)
}
}
Try adding a
.listRowPlatterColor(.clear)
put it inside the list like this...
struct ContentView: View {
var body: some View {
List {
Image("cat-1")
.resizable()
.scaledToFit()
.listRowPlatterColor(.clear)
Image("cat-2").resizable().scaledToFit().padding(5).background(Color.white)
Image("cat-1")
.resizable()
.scaledToFit()
.padding(.top, 5)
.background(Color.white)
}
.listStyle(CarouselListStyle())
}
}
I put it in the first item and left it off of the second and third so that you could see the difference. This other question can provide some more details:
How to style rows in SwiftUI List on WatchOS? .
You should then be able to style it however you like.

Navigation View Formatting Trouble

I'm a newbie, using XCode 13.0 to create a very basic app that needs to have a Settings view. I'd like to navigate to the Settings view on tapping a label. To do that, it seemed sensible to use a NavigationView with a NavigationLink.
Unfortunately, I'm encountering a formatting issue that creates a mess of the HStack in which the Setting label (gear icon) resides, as show below:
This is what I want, a result of the following code:
HStack(spacing: 25) {
... other labels
Label ("", systemImage: "gear")
.foregroundColor(.gray)
.font(.title)
.onTapGesture(perform: {
// Set a state variable that triggers an extension
// that brings up the SettingsView
})
}
This is what happens when NavigationView encapsulates the gear icon label. Note the vertical and horizontal white space around it.
HStack(spacing: 25) {
... other labels
NavigationView {
NavigationLink(destination: SettingsView()) {
Label ("", systemImage: "gear")
.foregroundColor(.gray)
.font(.title)
}.navigationBarTitle(Text(""))
}
}
I've, literally, spent weeks (sporadically) on this issue, looking up dozens of answers and trying various formatting options, without luck. I've also tried encapsulating parent and grandparent stacks into the NavigationView. To no avail. Surely, this is something trivial. Can somebody point me in the right direction?
p.s. there are other issues in that that Navigation link opens as a sub-window; I plan to tackle that later.
Edit: Right, so I tried using Yrb's code:
HStack(spacing: 25) {
... other labels
NavigationView {
NavigationLink(destination: Text("Linked View")) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.title)
}
.fixedSize()
.background(Color.red)
}
]
Unfortunately, there's no substantive change...
In diagnosing these sort of issues, it helps to throw a .background() with a color on. You can then see the issue. In this case, it was twofold, one, you need to use a .fixedSize to shrink the view to its smallest dimensions necessary. That would leave you with the icon plus a little space. That was due to you using a label as it was leaving a spot for the Text("") that you used as a fill in. Since you only want the image, use Image(systemName:) The code then comes out like this:
struct NavLinkNoSpace: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Linked View")) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.title)
}
.fixedSize()
// Setting this shows you what space you are using. Remove it when you are done
.background(Color.red)
}
}
}
A couple more things. If you have not ever set the NavigationTitle, you don't need to set it to "". In your example, there was no title, so I simply removed it and there was no effect.
More importantly, and it was addressed by some of the comments, you should only have one NavigationView in the view hierarchy. As long as you are in the hierarchy, you do not need to wrap things like NavigationLink to have them work. You can always throw one around your view call in the preview provider if you are in a child view, to show what things look like, and to test NavigationLinks, etc., but do not just put them in to your main code. It will lead to undesirable outcomes.
To summarize what worked to fix the primary problem, that of formatting: The key was in figuring what to encapsulate within the NavigationView. My mistake was to assume that only the NavigationLink needed to be in the NavigationView.
What worked was to place all the contents of the body into the NavigationView, like below:
var body: some View {
NavigationView {
VStack(spacing: -10) {
Text(appName)
.font(.largeTitle)
.foregroundColor(.blue)
.padding(.bottom)
// ...
// includes a bunch of VStacks and HStacks
// ... and finally
NavigationLink(destination: SettingsView()) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.title)
// ... more stuff
// ... and finally
}.padding(.top, -100) // NavigationView
} // body

Swipe back when using a custom navigation bar and a TabView SwiftUI

I have a SwiftUI app which uses a custom navigation bar. Because of that, I need to handle the back navigation separately (both the back button and the swipe gesture). Everything went fine up until now, when I need to use a TabView to swipe between pages. The code below illustrates what I'm trying to achieve:
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Go to second view")
}
}
}
}
SecondView.swift
import SwiftUI
// Being able to go back by swiping
// https://stackoverflow.com/questions/59921239/hide-navigation-bar-without-losing-swipe-back-gesture-in-swiftui
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
struct SecondView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack(alignment: .leading) {
Image(systemName: "chevron.left")
.foregroundColor(Color(.systemBlue))
.font(.title2)
.onTapGesture {
self.presentationMode.wrappedValue.dismiss()
}
.padding()
TabView {
Text("Test 1")
.tag(1)
Text("Test 2")
.tag(1)
Text("Test 3")
.tag(3)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
.background(Color(.systemGray6))
.navigationBarHidden(true)
}
}
Note that I didn't add the custom navigation bar for the second view, I've just hidden the default navigation bar, as the custom bar is not needed to solve this problem.
When the user is inside the SecondView and presses the back button, everything works as expected. The problem appears when he tries to swipe back, as the swipe back gesture is captured by the TabView. I want to keep the 'swipe-between-pages' functionality of the TabView while being able to go back to ContentView when the user swipes right from the leftmost part of the screen.
This problem only appears when using TabViews, other types of content handle the swipe back gesture without problems.
To solve this problem, I could add a horizontal padding to the TabView like this:
TabView {
// content
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.padding(.horizontal)
and then the user would have some space to swipe back, but this solution is not so good when some views inside the TabView need to take the whole width of the screen.
Is there any way to handle the swipe back gesture in this particular case? Maybe another possible solution would be customizing the TabView to ignore the drag gesture when the first view is presented and a swipe right gesture is captured (I don't know how to implement that).
I ran into this same problem. I solved it by wrapping my first tabview in with a geometryReader. When the bounds of that view is more than a quarter off the screen, I dismiss the view.

SwiftUI TabView Animation Not giving any animation

I'm trying to create a TabView Slider in SwiftUI with 3 modals that will onboard a user. The default PageTabViewStyle() is a little basic and I'm wanting it to animate with the default slide speed etc.
I've tried appending animation along with the transition from what I've seen online including StackOverflow but it doesn't work.
Here's what I currently have:
ZStack {
Color.black
TabView(selection: $currentTab) {
ForEach(OnboardingData.list) { viewData in
OnboardingModal(data: viewData)
.tag(viewData.id)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.animation(.easeInOut)
.transition(.slide)
}

Make NavigationLink destination view executed "onShow"

I am building a fitness app where user selects one of the programs from the list and when he clicks on selected program a new view appears with video playing (video has it's countdown timer on it).
I built the screen using NavigationView/NavigationLink each having it's "destination view" with it's own params.
let sets: [TrainingSet]
init() {
self.sets = [set1,set2,set3]
}
var body: some View {
NavigationView {
VStack {
ForEach(self.sets) { set in
NavigationLink(destination: ExerciseVideoView(items: set.items).navigationBarBackButtonHidden(true)) {
VStack {
Image("group-\(set.image)")
.resizable()
.renderingMode(.original)
.frame(height: 200, alignment: .leading)
.overlay(
Text(set.purpose)
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
)
Color.blue
.frame(maxWidth: .infinity)
.overlay(
Text(set.purpose)
.foregroundColor(.white)
)
}
}
}
}
}
}
1) I noticed that when the parent view is built, all destination views (ExerciseVideoView) get executed even before user clicks on corresponding button. My countdown timers start in background. I was supposed to see them launched when user click on NavigationLink and new view is "executed". Is that correct behaviour? Can I make destination view "executed/started" when they are shown?
2) The second problem is I wanna show the images with blue buttons below each of them (I put them both inside a VStack container). But when I launch my app only the images are shown but "Color.blue" rectangles are not visible. Why is that? How to make them visible as well?