SwiftUI TabView Embedded In Another View Problem - swift

For a few time now, I have been playing with SwiftUI. I'd like to make it clear that I do have a Java and JS background, but no any Swift experience.
So the idea is:
I'd like to have a view (referred later on as main view) where the screen is divided into two parts (views). The top one will be used just like a header - picture with maybe a footer (the frosted footer in the screenshots).
Then the second part of the screen I want it to be another View that is actually a TabView. See attached screenshots of unscrolled view of the main view and
scrolled view of the main view
Currently, what I have now is:
struct CarDetailsView: View {
let car: Car
var body: some View {
ScrollView {
HeadingView(car: car)
CarDescriptionView(car: car)
.offset(y: -36)
}
.ignoresSafeArea()
}
}
Which works fine (as in the screenshots), where HeadingView is the picture with the footer, and then CarDescriptionView is the "second" view of the screen with the description. This is also the part that I'd like to make it TabView.
I thought, I can just wrap the CarDescription View inside a TabView, just like that:
struct CarDetailsView: View {
let car: Car
var body: some View {
ScrollView {
HeadingView(car: car)
TabView {
CarDescriptionView(car: car)
.offset(y: -36)
.tabItem {
Label("Description", systemImage: "car")
}
}
}
.ignoresSafeArea()
}
}
However, then I am getting this thing happening here
Note, the structure of my other View are:
struct HeadingView: View {
var car: Car
var body: some View {
VStack(spacing: 0) {
Text("25 300EUR")
.foregroundColor(.white)
.font(.title)
.bold()
.padding(.horizontal, 12)
.padding(.vertical, 4)
.background(Color.init(hexadecimal: "66B054"))
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
.padding(16)
HStack {
Image(systemName: "car")//todo: make logo go here
.padding(.leading, 16)
.padding(.trailing, 16)
VStack(alignment: .leading, spacing: 4) {
Text("\(car.make) \(car.model)")
.font(.title2)
.bold()
HStack {
Text("\(String(car.odometer / 1000))k km |")
.font(.caption)
Text("\(String(car.year)) |")
.font(.caption)
Text("\(String(car.engine.horsepower))bhp |")
.font(.caption)
Text("20km away")
.font(.caption)
}
}
Spacer()
}
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.top, 12)
.padding(.bottom, 38)
.background(
VisualEffectBlurView(blurStyle: .systemThinMaterialDark)
)
}
.frame(maxWidth: .infinity, minHeight: 500, maxHeight: 500)
.background(
Image(car.images[0])
.resizable()
.aspectRatio(contentMode: .fill)
)
.mask(
RoundedRectangle(cornerRadius: 0, style: .continuous)
)
}
}
and
struct CarDescriptionView: View {
let car: Car
var body: some View {
ScrollView {
Text("Description")
.font(.title)
.bold()
.padding(.top, 32)
.padding(.leading, 16)
.padding(.bottom, 16)
.frame(maxWidth: .infinity, alignment: .leading)
Text(car.description)
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
}
.background(.white)
.mask(
RoundedRectangle(cornerRadius: 30, style: .continuous)
)
}
}
Please note that I have also tried to place the TabView inside the CarDetailsView, but again with the same result.
Changing the ScrollView to VStack in the main component results in somewhat working view. However, then I do not get any scrolling of the header View (the car image), but only I am able to scroll within the tabbed view.
As I said at the beginning, I am really new to this, so I am not even sure if the concept is correct or not.

Related

How to align text to the left and in the middle of a view

I have two Texts: Text("1") and Text("2") in an HStack. I want Text("1") to be in the leftmost part of ContentView and Text("2") to be in the exact horizontal center of ContentView.
Here's my code:
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("1")
.multilineTextAlignment(.leading)
.frame(
alignment: .leading
)
Text("2")
.multilineTextAlignment(.center)
.frame(
alignment: .center
)
}
.frame(width: .infinity)
}
}
When I run this though, both Text("1") and Text("2") remain in the general horizontal center of ContentView. Not sure what I'm doing wrong.
Here's another way you can do this by using ZStack. Second text will always be in exact centre.
ZStack {
HStack {
Text("1")
Spacer()
}
HStack {
Spacer()
Text("2")
Spacer()
}
}
.padding()
Try the below code:
HStack {
Text("1")
.multilineTextAlignment(.leading)
Text("2")
.multilineTextAlignment(.center)
.frame(minWidth: 0, maxWidth: .infinity)
}
Add a Spacer to your HStack after each Text. This will give you the first Text at the left and the second starting in the middle
HStack {
Text("1")
.multilineTextAlignment(.leading)
.frame(alignment: .leading)
.padding(.leading)
Spacer()
Text("2")
.multilineTextAlignment(.center)
.frame(alignment: .center)
.padding(.trailing)
Spacer()
}

How to do a "reveal"-style collapse/expand animation in SwiftUI?

I'd like to implement an animation in SwiftUI that "reveals" the content of a view to enable expand/collapse functionality. The content of the view I want to collapse and expand is complex: It's not just a simple box, but it's a view hierarchy of dynamic height and content, including images and text.
I've experimented with different options, but it hasn't resulted in the desired effect. Usually what happens is that when I "expand", the whole view was shown right away with 0% opacity, then gradually faded in, with the buttons under the expanded view moving down at the same time. That's what happened when I was using a conditional if statement that actually added and removed the view. So that makes sense.
I then experimented with using a frame modifier: .frame(maxHeight: isExpanded ? .infinity : 0). But that resulted in the contents of the view being "squished" instead of revealed.
I made a paper prototype of what I want:
Any ideas on how to achieve this?
Something like this might work. You can modify the height of what you want to disclose to be 0 when hidden or nil when not so that it'll go for the height defined by the views. Make sure to clip the view afterwards so the contents are not visible outside of the frame's height when not disclosed.
struct ContentView: View {
#State private var isDisclosed = false
var body: some View {
VStack {
Button("Expand") {
withAnimation {
isDisclosed.toggle()
}
}
.buttonStyle(.plain)
VStack {
GroupBox {
Text("Hi")
}
GroupBox {
Text("More details here")
}
}
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
HStack {
Text("Cancel")
Spacer()
Text("Book")
}
}
.frame(maxWidth: .infinity)
.background(.thinMaterial)
.padding()
}
}
No, this wasn't trying to match your design, either. This was just to provide a sample way of creating the animation.
Consider the utilization of DisclosureGroup. The following code should be a good approach to your idea.
struct ContentView: View {
var body: some View {
List(0...20, id: \.self) { idx in
DisclosureGroup {
HStack {
Image(systemName: "person.circle.fill")
VStack(alignment: .leading) {
Text("ABC")
Text("Test Test")
}
}
HStack {
Image(systemName: "globe")
VStack(alignment: .leading) {
Text("ABC")
Text("X Y Z")
}
}
HStack {
Image(systemName: "water.waves")
VStack(alignment: .leading) {
Text("Bla Bla")
Text("123")
}
}
HStack{
Button("Cancel", role: .destructive) {}
Spacer()
Button("Book") {}
}
} label: {
HStack {
Spacer()
Text("Expand")
}
}
}
}
The result looks like:
I coded this in under 5 minutes. So of course the design can be optimized to your demands, but the core should be understandable.
import SwiftUI
struct TaskViewCollapsible: View {
#State private var isDisclosed = false
let header: String = "Review Page"
let url: String
let tasks: [String]
var body: some View {
VStack {
HStack {
VStack(spacing: 5) {
Text(header)
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.black)
.padding(.top, 10)
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
Text(url)
.font(.system(size: 12, weight: .regular))
.foregroundColor(.black.opacity(0.4))
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
Image(systemName: self.isDisclosed ? "chevron.up" : "chevron.down")
.padding(.trailing)
.padding(.top, 10)
}
.onTapGesture {
withAnimation {
isDisclosed.toggle()
}
}
FetchTasks()
.padding(.horizontal, 20)
.padding(.bottom, 5)
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.black.opacity(0.2))
)
.frame(maxWidth: .infinity)
.padding()
}
#ViewBuilder
func FetchTasks() -> some View {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0 ..< tasks.count, id: \.self) { value in
Text(tasks[value])
.font(.system(size: 16, weight: .regular))
.foregroundColor(.black)
.padding(.vertical, 0)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.frame(maxHeight: CGFloat(tasks.count) * 20)
}
}
struct TaskViewCollapsible_Previews: PreviewProvider {
static var previews: some View {
TaskViewCollapsible(url: "trello.com", tasks: ["Hello", "Hello", "Hello"])
}
}

Swift UI with Nested Navigation Views

I am new to SwiftUI and Stack Overflow as well, so any help here is appreciated. I am currently making an app that has nested Navigation Views. However, one issue I am having is that when I am using these nested Views, the Navigation Titles (ex. the back button) are lining below each other, forming a Navigation Bar that is quite tall. I looked at this link and it used a List, but I do not want my app to be in this format. I would like to use text elements that are formatted in different colors and font sizes (please see the attached screenshot), however, this thread only has this list function, not in the format I would like it. Please help me figure out how to get rid of these extra navigation bars!
Text Elements:
Nested Navigation Bar:
Here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
Image("img6").resizable().aspectRatio(contentMode: .fill).ignoresSafeArea()
VStack {
Text("SLED").font(.largeTitle).fontWeight(.heavy).padding().background(Color.blue).cornerRadius(10.0)
Spacer()
Text("Shelf Life Expiry Date Tracker").font(.title).fontWeight(.bold)
Spacer()
ZStack {
Image(systemName: "cloud")
.padding()
.font(.system(size: 90))
//try to fix this
//Image("logo1")
Image(systemName: "clock.fill")
.padding()
.font(.system(size: 40))
}
NavigationLink(destination: SeeTestKit()) {
Text("See Test Kit")
}.foregroundColor(Color(red: 0.0, green: 0.0, blue: 0.0))
.padding(20)
.background(Color.pink)
.cornerRadius(10.0)
//Spacer()
HStack {
Spacer()
Image(systemName: "bag")
.padding()
.font(.system(size: 40))
Spacer()
Image(systemName: "alarm.fill")
.padding()
.font(.system(size: 40))
Spacer()
Image(systemName: "calendar")
.padding()
.font(.system(size: 40))
Spacer()
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}```
```import SwiftUI
struct SeeTestKit: View {
var body: some View {
NavigationView {
ZStack {
Image("background2")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
VStack {
Spacer()
Image("testkit")
.resizable()
.aspectRatio(contentMode: .fit)
Spacer()
Text("The test kit serves as a supplement to the SLED Tracking System.")
.font(.title)
.multilineTextAlignment(.center)
Spacer()
Text("The test kit make the observation process more efficient by not requiring you to find your own materials.")
.font(.title2)
.multilineTextAlignment(.center)
Spacer()
Text("It also serves as an alternative to \npeople who may not have access \nto the technology required for the app.").font(.title2).multilineTextAlignment(.center)
Spacer()
Group {
NavigationLink(destination: TestKitMaterials()) {
Text("Materials")
}.foregroundColor(Color(red: 0.0, green: 0.0, blue: 0.0))
.padding(20)
.background(Color.blue)
.cornerRadius(10.0)
.navigationBarTitle("Test Kit", displayMode: .inline).padding()
Spacer()
}
}
}
}
}
}
struct SeeTestKit_Previews: PreviewProvider {
static var previews: some View {
SeeTestKit()
}
}```
```import SwiftUI
struct TestKitMaterials: View {
var body: some View {
ZStack {
Image("bckgrd")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
VStack {
Text("Test Kit Materials")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
.padding(.all, 9.0)
.background(Color(red: 0.5, green: 0.6, blue: 0.9, opacity: 1.0)) .cornerRadius(10.0)
//FIX this
//Text("Safety Glasses").bold()
Spacer()
Group {
Spacer()
Text("Safety glasses were included to prevent \ncontamination or illness from \npotentially spoiled foods.")
.multilineTextAlignment(.center)
.padding(.all, 3.0)
Text("A thermometer was included to test \ntemperature of foods to test for spoilage \nat a certain temperature.")
.multilineTextAlignment(.center)
.padding()
}
Group {
Text("pH strips were used to test the pH of \nthe milk and eggs.")
.multilineTextAlignment(.center)
.padding()
Text("The flashlight and ruler are included for \nthe user to measure the width of the air\n sac of the egg in a dark room.")
.multilineTextAlignment(.center)
.padding()
}
Group {
Text("A pipette is needed to extract samples \nof the food to test certain characteristics.")
.multilineTextAlignment(.center)
.padding()
Text("The test tube provides a separate vessel \nto hold the milk to make observations.")
.multilineTextAlignment(.center)
.padding()
Text("A checklist is included as a guideline for the \nuser to measure characteristics and to\n measure spoilage in the absence of the app.")
.multilineTextAlignment(.center)
.padding()
}
Spacer()
Image(systemName: "img")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}
}
struct TestKitMaterials_Previews: PreviewProvider {
static var previews: some View {
TestKitMaterials()
}
}```
Thank you!
As stated earlier in the comments you need to remove a NavigationView. And almost always you'll want to remove any NavigationView on the children views.
Essentially what's happening is you are double stacking your NavViews and can cause some really funky behavior.
Read more on NavigationView

SwiftUI bug with ScrollView appearance to the scene

I have problem in SwiftUI. I created ScollView and post into some Views. (News Articles). When i launch app - ScrollView and content inside appears in dioganal way, like animation. But i need to launch app and get static ScrollView with views inside it, without any movements.
Also the same problem occurred when i open sheet view with ScrollView and Text Blocks inside. It also appers first 0.3s like dioganaly way. But how can i achive static ? Thank you all )
Place i use sheet controller (Also this block in ScrollView)
ScrollView{
ArticlePreView(image: "one", category: "Category", heading: "Header", author: "some text").onTapGesture {
self.showingDetail.toggle()
}.sheet(isPresented: $showingDetail) {
Home()
}
}
Image to understand
Video of problem
And this is place of my sheet controller
ScrollView() {
VStack {
HStack {
Text("Soem long title")
.font(.title)
.fontWeight(.bold)
Spacer()
Button(action: {
}) {
HStack {
Image(systemName: "heart.fill")
.foregroundColor(Color.gray)
}
.padding()
}
.background(
Circle()
.fill(Color.neuBackground)
)
.shadow(color: .dropShadow, radius: 15, x: 10, y: 10)
.shadow(color: .dropLight, radius: 15, x: -10, y: -10)
}
.padding(.horizontal, 35)
.padding(.top, 25)
Text("long text")
.multilineTextAlignment(.leading)
.foregroundColor(.gray)
.padding(.horizontal, 30)
.padding(.top, 20)
Text("long text")
.multilineTextAlignment(.leading)
.foregroundColor(.gray)
.padding(.horizontal, 30)
.padding(.top, 20)
Text("long text")
.multilineTextAlignment(.leading)
.foregroundColor(.gray)
.padding(.horizontal, 30)
.padding(.top, 20)
Text("long text")
.multilineTextAlignment(.leading)
.foregroundColor(.gray)
.padding(.horizontal, 30)
.padding(.top, 20)
Spacer(minLength: 50)
}
}.frame(width: UIScreen.main.bounds.size.width)
Fixed using
.frame(width: UIScreen.main.bounds.size.width)
.fixedSize()
SwiftUI is not adaptive

Is onAppear and onDisappear in a NavigationView of a SwiftUI app behaving as expected?

I would like to know if the behavior of onAppear and onDisappear in SwiftUI (Xcode 11 beta 6 when I wrote this) is what a developer would find more useful or it is just being more a problem than a feature.
Right now, if we use a cascaded navigation as you will find in the sample code I attach (that compiles and runs fine in Xcode 11b6), a console output of a user navigating back and forth would just trigger onAppear only in the case of a new view load in the forward direction (meaning going deeper).
In the navigation:
Root -> NestedView1 -> NestedView2 -> NestedView3 ,
when adding a debug helper to each view stage,
.onAppear(perform: {print("onAppear level N")})
.onDisappear(perform: {print("onDisappear level N")})
debug console would show
onAppear root level 0
onAppear level 1
onAppear level 2
onAppear level 3
(No onDisappear triggering)
but travelling back
Root <- NestedView1 <- NestedView2 <- NestedView3
debug console would show ... nothing
(No onAppear or onDisappear triggering)
struct NestedViewLevel3: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Spacer()
Text("Level 3")
Spacer()
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Back")
.padding(.horizontal, 15)
.padding(.vertical, 2)
.foregroundColor(Color.white)
.clipped(antialiased: true)
.background(
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.blue)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
)
}
Spacer()
}
.navigationBarBackButtonHidden(false)
.navigationBarTitle("Level 3", displayMode: .inline)
.onAppear(perform: {print("onAppear level 3")})
.onDisappear(perform: {print("onDisappear level 3")})
}
}
struct NestedViewLevel2: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Spacer()
NavigationLink(destination: NestedViewLevel3()) {
Text("To level 3")
.padding(.horizontal, 15)
.padding(.vertical, 2)
.foregroundColor(Color.white)
.clipped(antialiased: true)
.background(
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.gray)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
)
.shadow(radius: 10)
}
Spacer()
Text("Level 2")
Spacer()
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Back")
.padding(.horizontal, 15)
.padding(.vertical, 2)
.foregroundColor(Color.white)
.clipped(antialiased: true)
.background(
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.blue)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
)
}
Spacer()
}
.navigationBarBackButtonHidden(false)
.navigationBarTitle("Level 2", displayMode: .inline)
.onAppear(perform: {print("onAppear level 2")})
.onDisappear(perform: {print("onDisappear level 2")})
}
}
struct NestedViewLevel1: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Spacer()
NavigationLink(destination: NestedViewLevel2()) {
Text("To level 2")
.padding(.horizontal, 15)
.padding(.vertical, 2)
.foregroundColor(Color.white)
.clipped(antialiased: true)
.background(
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.gray)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
)
.shadow(radius: 10)
}
Spacer()
Text("Level 1")
Spacer()
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Back")
.padding(.horizontal, 15)
.padding(.vertical, 2)
.foregroundColor(Color.white)
.clipped(antialiased: true)
.background(
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.blue)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
)
}
Spacer()
}
.navigationBarBackButtonHidden(false)
.navigationBarTitle("Level 1", displayMode: .inline)
.onAppear(perform: {print("onAppear level 1")})
.onDisappear(perform: {print("onDisappear level 1")})
}
}
struct RootViewLevel0: View {
var body: some View {
NavigationView {
VStack {
Spacer()
NavigationLink(destination: NestedViewLevel1()) {
Text("To level 1")
.padding(.horizontal, 15)
.padding(.vertical, 2)
.foregroundColor(Color.white)
.clipped(antialiased: true)
.background(
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.gray)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
)
.shadow(radius: 10)
}
Spacer()
}
}
.navigationBarTitle("Root level 0", displayMode: .inline)
.navigationBarBackButtonHidden(false)
.navigationViewStyle(StackNavigationViewStyle())
.onAppear(perform: {print("onAppear root level 0")})
.onDisappear(perform: {print("onDisappear root level 0")})
}
}
struct ContentView: View {
var body: some View {
RootViewLevel0()
}
}
}
Now, would a developer rather have onAppear and onDisappear:
1) Triggered for the purpose of launching actions that need to be performed only once and only when the user travels forward, like in the current observed behavior.
2) Triggered each time the view appears, more like the action name seems to mean, be it backwards, forwards and any number of times.
I'd take the option 2, simple and brutal (and what I currently need), but I am quite a naive newbie at NavigationView, and option 2 may break a lot of established paradigms I'm not taking into account.
Your feedback will help me find out if the corresponding Feedback Assistant case for SwiftUI is on legitimate grounds.
It was a bug on Apple's end.
.onAppear() now works as it is supposed to in iOS 13.1 and Xcode 11 Beta 7.
When navigating forwards and backwards to the NavigationView, .onAppear() will trigger.
Starting Xcode 11.2 beta (released 02/10/2019), both methods (including .onDisappear()) are being correctly triggered.
Note that .onDisappear() was not being triggered until this beta release.
Both commands:
.onDisappear(perform: ) & .onAppear(perform: )
are bugging the NavigationView (.navigationBarTitle) feature:
.navigationBarTitle("Opciones", displayMode: .inline)
When used the navigationBarTitle is removed or not working in the View where you use invoke it. This happens using BOTH:
.onDisappear(perform: )
.onAppear(perform: )
Using Xcode Version 12.2 (12B45b)