SwiftUI - Navigation Link transition is slow when tapping on it - swift

I'm developing a SwiftUI App with Xcode 12.0.
My HomeView simply contains a NavigationLink (within a NavigationView) that push to a second View containing a List of more than 4.000 elements previously downloaded from a server (when tapping the navigation link the download is already completed).
The issue is that when I tap it takes almost 2 seconds to show the next page. The same thing happen when I tap the iOS Back button.
In other hands, doing the same thing without a big List makes it fast as it should always be.
The HomeView Navigation link is this:
ScrollView(.vertical, showsIndicators: true, content: {
ForEach(firstHomeCard) { homeCard in
NavigationLink(destination: ExoplanetsDatabaseView()) {
HomeCard(homeCard: homeCard, geo: geo.size.width)
}.buttonStyle(PlainButtonStyle())
}
}
This is my List code in the second View:
List {
ForEach(HttpResponseHolder.shared.exoplanets.filter({ searchText.isEmpty ? true : $0.pl_name.contains(searchText) }), id: \.self) { exoplanet in
NavigationLink(destination: ExoplanetDetailView(exoplanet: exoplanet)) {
ExoplanetCard(exoplanet: exoplanet)
}.buttonStyle(PlainButtonStyle())
}.listRowBackground(Color.clear)
.padding(.trailing, -15)
.padding(.leading, 2)
.padding(.top, 2)
.padding(.bottom, 2)
}.id(UUID())
Is that a SwiftUI bug that can't handle many elements in a List?
UPDATE
ExoplanetCard's body
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 22.0)
.opacity(0.8)
.foregroundColor(Color(red: 31/255, green: 40/255, blue: 45/255))
HStack(alignment:.center, spacing: 20, content: {
Spacer()
imageForExoplanet(exoplanet: exoplanet)
.resizable()
.frame(width: 120, height: 120, alignment:.center)
.aspectRatio(contentMode: .fit)
.opacity(0.9)
.clipShape(Circle())
Divider()
VStack(spacing:5) {
Text(exoplanet.pl_name)
.foregroundColor(.white)
.font(.headline)
.lineLimit(2)
planetTypeTextReturner(exoplanet: exoplanet)
.foregroundColor(.white)
.font(.body)
.lineLimit(2)
}
Spacer()
})
}
}
ExoplanetDetailView is just an empty view so far.
EDIT 2
So by making my data model conforming to Identifiable e by giving it an id property of type UUID() and removing the id:.self the performance are now better. Still not perfect but at least improved a bit.

Related

How to use swipe actions without list and using indices swiftui

Im making a todo app, and want a swipe to delete each task.
ScrollView {
ForEach(tasks.indices, id: \.self) { index in
TextField("Activity here", text: $tasks[index])
.cornerRadius(20)
.foregroundColor(textColor)
.background(backgroundColor)
.padding(.leading, 10)
.padding(.trailing, 10)
.padding(.top, 5)
}
}.frame(height: 600, alignment: .topLeading)
I’ve tried .onDelete and .swipeactions, but can’t figure out how to do it without a list.
I think you must to use List for SwiftUI and you can use the onDelete to delete your tasks
Your code will be something like this:
var body: some View {
ScrollView {
List{
ForEach(tasks.indices, id: \.self) { index in
TextField("Activity here", text: $tasks[index])
.cornerRadius(20)
.foregroundColor(Color.white)
.background(Color.black)
.padding(.leading, 10)
.padding(.trailing, 10)
.padding(.top, 5)
}
.onDelete{_ in self.tasks.remove(at: 0)}
}.frame(height: 600, alignment: .topLeading)
}
}
However if you decide to create this app, but not in SwiftUI I can recommend for you 'SwipeCellKit' cocoapod.
https://github.com/SwipeCellKit/SwipeCellKit/blob/develop/Guides/Advanced.md
with tableviews you can create this app for cool. Good luck.

SwiftUI TabView Embedded In Another View Problem

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.

Variable assignment with mutating functionality

So I'm fairly new to SwiftUI & Swift (Javascript background) and I am going through the SwiftUI tutorial on Apple website.
When you get through the first section, you'll end up with LandmarkDetail.swift body ends up something like this:
var body: some View {
VStack {
MapView(coordinate: landmark.locationCoordinate)
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
CircleImage(image: landmark.image)
.offset(x: 0, y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
HStack {
Text(landmark.name)
.font(.title)
Button(action: {
self.userData.landmarks[self.landmarkIndex].isFavorite.toggle()
}, label: {
if (self.userData.landmarks[self.landmarkIndex].isFavorite) {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
else {
Image(systemName: "star")
.foregroundColor(.gray)
}
})
}
HStack(alignment: .top) {
Text(landmark.park)
.font(.subheadline)
Spacer()
Text(landmark.state)
.font(.subheadline)
}
}
.padding()
Spacer()
}
.navigationBarTitle(Text(landmark.name), displayMode: .inline)
}
All good. So I decide to make some small incremental changes just to test the language. I noticed self.userData.landmarks[self.landmarkIndex] is used twice so I simply added that as a variable within the body and had the view return explicitly.
var userDataLandmark: Landmark = self.userData.landmarks[self.landmarkIndex]
return VStack {
No big deal. Then I replaced the repeating landmark:
Button(action: {
userDataLandmark.isFavorite.toggle()
}, label: {
if (userDataLandmark.isFavorite) {
Image(systemName: "star.fill")
Everything looked fine on the surface, but the toggle() functionality no longer works. Generally assuming this is some referencing/pointer stuff but If anyone could explain why that'd be great.
When you did:
var userDataLandmark: Landmark = self.userData.landmarks[self.landmarkIndex]
you created a copy of self.userData.landmarks[self.landmarkIndex], because Landmark is a value-type (a struct).
Now, when you do:
userDataLandmark.isFavorite.toggle()
you're toggling the copy. So, it doesn't change the .landmarks property of the observed environment variable userData, which is what would have caused the view's body to be recomputed.

My TextField in Swift UI is Unresponsive to taps and clicks, text cannot be input, is there an issue with the code?

Hi I'm an intermediate level engineer with swift and swift UI.
Currently, I am having problems with the TextField(), it is unresponsive to taps and clicks, and hence the keyboard does not show up,
However, it does show up when it is one of two main elements within a VStack, but anymore than that and it does not work,
Is there an issue with the code? and can anyone find a fix or a work around for this?
Thanks, Miles
{
#State var textField = ""
var body: some View {
returnJoinModalContent()
}
func returnJoinModalContent() -> some View{
let accentColor = Color.blue
return VStack(alignment: .center){
HStack{
Text("Join Game")
.bold()
.font(.largeTitle)
.padding()
Spacer()
Button(action:{
//self.hideModal()
}){
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 50,height: 50)
.foregroundColor(accentColor)
.padding()
}
}
.padding(.bottom,170)
VStack(alignment: .leading){
TextField("Enter Game Code",text: self.$textField)
.font(.largeTitle)
Rectangle()
.frame(height: 6)
.foregroundColor(accentColor)
}
.padding()
HStack{
Button(action:{
//self.joinGame()
}){
ZStack{
RoundedRectangle(cornerRadius: 90)
.frame(width: 200,height: 70)
.foregroundColor(accentColor)
Text("Ready!")
.bold()
.font(.title)
.foregroundColor(Color.white)
}
}
}
.padding(.vertical,20)
HStack{
Image(systemName: "info.circle")
.resizable()
.frame(width:60,height:60)
.foregroundColor(accentColor)
Text("Ask your host for the game code, it can be found at the bottom of the player lobby")
.padding()
Spacer()
}
.padding(.top,60)
.padding(.horizontal,20)
Spacer()
}
.padding()
}
}
This seems to be one of those things in swiftUI that is difficult to track down. I found the problem is your ready button. More specifically the label being a ZStack with a colored rounded rectangle as the background. If you replace your button's label with the following your text field should be selectable.
Text("Ready!")
.bold()
.font(.title)
.foregroundColor(Color.white)
.frame(width: 200,height: 70)
.background(accentColor)
.mask(RoundedRectangle(cornerRadius: 90))
I've found that overlay on the TextField or the container of TextField might cause it to be unresponsive
you should remove it and replace it with ZStack

List inside ScrollView is not displayed on WatchOs

I have a list inside a scrollview and it is not displaying below the image and the buttons. I've also tried to put the list and other items inside of a VStack and that allows me to see one item of the list at the time opposed to scrolling past the Image and buttons to show the whole list.
ScrollView{
Image(uiImage: self.image)
.resizable()
.frame(width: 80, height: 80)
.scaledToFit()
Text("\(name)")
.lineLimit(2)
HStack{
Button(action: {
print("button1")
}){
Image(systemName: "pencil")
}
Button(action: {
print("button 2")
}){
Image(systemName: "trash")
}
}
List{
ForEach(self.items, id: \.self) { item in
VStack{
Text(item.name)
.font(.headline)
.lineLimit(1)
Text(item.subname)
.font(.subheadline)
.lineLimit(1)
}
}
}
}
.navigationBarTitle(Text("Tittle"))
.edgesIgnoringSafeArea(.bottom)
Ive also tried to add .frame( minHeight: 0, maxHeight: .infinity)
to the list to force it to have its whole height and that did not work either. Any suggestions or might this be a swiftUI bug ?
EDIT
I just realized Im getting this error when scrolling:
APPNAME Watch Extension[336:60406] [detents] could not play detent NO, 2, Error Domain=NSOSStatusErrorDomain Code=-536870187 "(null)", (
{
Gain = "0.01799999922513962";
OutputType = 0;
SlotIndex = 4;
},
{
Gain = "0.6000000238418579";
OutputType = 1;
SlotIndex = 5;
}
)
Indicate some height for your List like
List{
ForEach(self.items, id: \.self) { item in
VStack{
Text(item.name)
.font(.headline)
.lineLimit(1)
Text(item.subname)
.font(.subheadline)
.lineLimit(1)
}
}
}.frame(minHeight: 200, maxHeight: .infinity)
Have you tried putting the List inside of a GeometryReader and setting the frame there?
I got the same error with UIKit. The error message is related to haptic feedback, see here.
It says that a haptic feedback could not be played, probably type 2, i.e. directionDown, see here.
Since my code does not call play(_:), it must be called by watchOS itself.
The reason for the error might be very fast scrolling, which could result in too frequent calls to play(_:) that cannot be handled properly. The docs say:
Do not call this method multiple times in quick succession. If the
haptic engine is already engaged when you call this method, the system
stops the current feedback and imposes a minimum delay of 100
milliseconds before engaging the engine to generate the new feedback.
If this is really an effect of watchOS, I guess you cannot do anything to avoid the error.