ScrollView + Custom View + ContextMenu animation glitch - SwiftUI - swift

Is there a workaround to fix these animation glitches? Glitch video
The animation glitch on scroll
The one where the view disappears for less than a second
The view semantics is:
TabView {
NavigationView {
ScrollView {
VStack {
ForEach(){
MyCustomView()
.contextMenu()
MyCustomView is below, if anyone would want to test it:
struct CardVew: View {
var title: String
var description: String
var name: String
var task: String
var done: Bool
let tapVibration = UIImpactFeedbackGenerator(style: .light)
#State var details = false
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 15)
.foregroundColor(Color("cardGray"))
.opacity(0.24)
VStack(alignment: .leading, spacing: .zero) {
titleBlock
descriptionBlock
bottomBlock
}
.padding([.leading, .trailing], 20)
.padding(.bottom, 15)
.padding(.top, 24)
}
.padding([.leading, .trailing], 12)
.sheet(isPresented: $details) {
CardSheetView(
title: title,
description: description,
task: task,
name: name
)
}
.onTapGesture {
tapVibration.impactOccurred()
details = true
}
.onAppear {
tapVibration.prepare()
}
}
private var titleBlock: some View {
HStack(alignment: .top) {
Text(title)
.font(.system(size: 22, weight: .bold))
.fixedSize(horizontal: false, vertical: true)
.frame(width: 244, alignment: .leading)
.padding(.bottom, 4)
if done {
Spacer()
Image("done")
.opacity(0.8)
}
}
}
private var bottomBlock: some View {
HStack(alignment: .bottom) {
HStack(alignment: .center) {
Image(systemName: "calendar")
Text("22.09")
}
Spacer()
Text(name)
.multilineTextAlignment(.trailing)
.opacity(0.6)
.font(.footnote)
.frame(width: 211, alignment: .trailing)
}
}
private var descriptionBlock: some View {
Text(description)
.opacity(0.8)
.fixedSize(horizontal: false, vertical: true)
.frame(width: 247, alignment: .leading)
.padding(.bottom, 38)
}
}
i tried to exclude all animations, sheet and anything that could cause such behaviour, but had no success

Related

How do I Fix the alignment of this swiftUI view

I'm trying to put a number of DateCardViews inside a ScrollView as shown below
How do I fix the alignment DateCardView, so all DateCardViews are anchored to the top and start at the same level?
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top) {
ForEach(dates.prefix(8), id: \.id) { date in
DateCardView(meetDate: date)
}
}
.padding(.horizontal, 16.0)
}
.padding(.bottom, 20.0)
}
}
struct DateCardView: View {
#State var meetDate: MeetDate
#State var swap = false
var isExpired: Bool {
Date.isAboutToExpire(date: meetDate.date)
}
var body: some View {
VStack {
Image(swap ? meetDate.user.profileImages.first! : meetDate.bgIamge)
.resizable()
.cornerRadius(16)
.overlay(
Image((swap ? meetDate.bgIamge : meetDate.user.profileImages.first!))
.resizable()
.cornerRadius(6)
.frame(width: 32, height: 40)
.padding(.bottom, 8)
.padding(.trailing, 8)
.onTapGesture {
swap.toggle()
}, alignment: .bottomTrailing
)
.frame(height: 154)
VStack(alignment: .leading) {
HStack {
Text(meetDate.title)
.font(.custom("SFCompactText-Medium", size: 18))
.lineLimit(2)
}
HStack {
Text(Date.yearString(date: meetDate.date))
Text(Date.timeString(date: meetDate.date))
.foregroundColor(isExpired ? Color("Expired") : Color.black)
Spacer()
}.font(.custom("SFCompactText-Medium", size: 14))
HStack {
Text(meetDate.length)
.font(.custom("SFCompactText-Medium", size: 12))
Text(meetDate.cost)
.font(.custom("SFCompactText-Medium", size: 12))
.foregroundColor(Color("cost"))
}
}
}.frame(width: 138, height: 235)
}
}
You can probably fix this by adding a Spacer at the bottom of your outer VStack, but as the code in your question won't compile I can't test it,
VStack {
Image(swap ? meetDate.user.profileImages.first! : meetDate.bgIamge)
//
VStack(alignment: .leading) {
// etc
}
Spacer(minLength: 0)
}.frame(width: 138, height: 235)
You can add Spacer() in your Vstack. I think that will work or you can change your alingment values.
VStack(alignment: .leading) {
//your code
Spacer()
}

Can't display custom pop-up because my tab view is in NavigationView

I want to display a pop-up in my view, where I will be able to display a menu where I can choose how I fell, then it will show what I have chosen and close itself. If I am adding it to the view and it's presenting It shows wrong and Tab Bar does not disappear. Can someone provide a better way to show a pop-up over Tab Bar? The logic is something like this: button pressed -> shows pop-up -> choose status -> shows another pop-up -> disappears.
Code's provided below:
// Smile face that used in question
struct SmileFace: View {
var text: String
var image: String
#Binding var current: String
var body: some View {
Button {
withAnimation {
current = text
}
} label: {
VStack {
Image(image)
.resizable()
.scaledToFill()
.frame(width: 46, height: 46)
Text(text)
.foregroundColor(.white)
}
}
}
}
// Check in answer view
import SwiftUI
struct CheckInAskView: View {
let didClose: () -> Void
var didChose: Bool = false
#State var current: String
var emotions = [
"GREAT" : "good",
"GOOD" : "happy",
"OK" : "moderate",
"BAD" : "sad",
"TERRIBLE" : "verysad"
]
var body: some View {
VStack {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color("navigation"))
.cornerRadius(15)
.frame(height: 601)
HStack(alignment: .top) {
ZStack(alignment: .top) {
Circle()
.fill(Color(red: 0.682, green: 0.384, blue: 0.486).opacity(0.10))
.frame(width: 173)
.offset(x: -173/2, y: -90/2)
.clipped()
}
Spacer()
Button {
didClose()
} label: {
ZStack {
Circle()
.fill(Color(red: 0.933, green: 0.933, blue: 0.933).opacity(0.30))
.frame(width: 24)
.clipped()
Image(systemName: "xmark")
.font(.system(size: 15))
.foregroundColor(Color(red: 0.762, green: 0.762, blue: 0.762))
}
.padding(10)
}
}
VStack(alignment: .center) {
Spacer()
Text("How do you feel now?")
.foregroundColor(.white)
.font(.custom("Manrope-Bold", size: 16))
HStack(spacing: 35) {
SmileFace(text: "GOOD", image: "good", current: $current)
}
.padding(.horizontal)
DoubleTextView(topText: "Recommendation for you", buttomText: "We have selected courses based on your goals and \nexperience", topTextSize: 16, buttomTextSize: 14)
BigFrameScrollViewHorizontal()
Spacer()
}
}
.frame(height: 601)
}
.frame(height: 601)
.transition(.move(edge: .bottom))
}
}
// View with all views
struct CheckInView: View {
#StateObject var sheetManager: SheetManager
var body: some View {
VStack {
HStack {
Text("How do you fell now?")
.foregroundColor(.white)
.font(.custom("Manrope-Bold", size: 16))
Spacer()
Button {
} label: {
ZStack {
HStack {
Text("Pass check in")
.padding([.top, .leading, .bottom])
.foregroundColor(.white)
.font(.custom("Manrope-Medium", size: 12))
Image(systemName: "chevron.right")
.foregroundColor(.white)
.font(.system(size: 15))
.padding(.trailing)
}
}
.background(Rectangle()
.fill(Color("active"))
.cornerRadius(100)
.frame(height: 26))
}
}
VStack {
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
}
HStack {
Button {
} label: {
ZStack {
Circle()
.fill(Color("navigation"))
.frame(width: 26)
Image(systemName: "chevron.left")
.foregroundColor(.white)
.font(.system(size: 14))
}
}
Button {
} label: {
ZStack {
Circle()
.fill(Color("navigation"))
.frame(width: 26)
Image(systemName: "chevron.right")
.foregroundColor(Color("inactive"))
.font(.system(size: 14))
}
}
}
.padding(.top)
}
.padding(.horizontal, 15.0)
}
}
struct CheckInView_Previews: PreviewProvider {
static var previews: some View {
CheckInView(sheetManager: SheetManager())
.background(.yellow)
}
}
If I have all of this, how can I create a pop-up over Tab View? Maybe a link to a similar problem?

SwiftUI, different Views in same GridView

I need to add two different types of view in same grid view, there is an alignment issue between these view as shown in give image below. The view which contains image is taking more space from top and bottom even after fixing the size
struct TestView: View {
#StateObject var vm = BusinessProfileViewModel()
#Environment(\.presentationMode) private var presentationMode
let columns = [
GridItem(.flexible(minimum: 100), spacing: 20),
GridItem(.flexible(minimum: 100), spacing: 20)
]
var body: some View {
loadView()
}
}
extension TestView {
func loadView() -> some View {
GeometryReader { geometry in
ZStack(alignment: .bottomTrailing) {
ScrollView(.vertical, showsIndicators: false) {
topBusinessImage(geometry: geometry)
VStack {
featureProduct(geometry: geometry)
}.padding()
}.edgesIgnoringSafeArea(.top)
}
}
}
func topBusinessImage(geometry: GeometryProxy) -> some View {
VStack(spacing: 0) {
// Profile Image Stack
// Banner Zstack
ZStack (alignment: .top) {
Image(ImageName.productPlaceholder.rawValue)
.resizable()
.frame(height: geometry.size.height / 2.5)
}
}
}
func featureProduct(geometry: GeometryProxy) -> some View {
VStack(alignment: .leading) {
// product and services button
HStack {
Text("Products & Services")
.font(.custom(Popins.bold.rawValue, size: 20))
Spacer()
Button {
print("list")
} label: {
Image(ImageName.list.rawValue)
.resizable()
.frame(width: 24, height: 24)
}.padding(5)
}
LazyVGrid(columns: columns, spacing: 30) {
BusinessProductCell()
ProductOnlyDetailCell()
ProductOnlyDetailCell()
BusinessProductCell()
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
view that contains image
import SwiftUI
struct BusinessProductCell: View {
var body: some View {
loadView()
}
}
extension BusinessProductCell {
func loadView() -> some View {
VStack(alignment: .leading) {
ZStack(alignment: .topLeading) {
Image(ImageName.business.rawValue)
.resizable()
.frame(height: 205)
.cornerRadius(10)
HStack {
Button {
print("fav")
} label: {
Image(systemName: "heart.fill")
.resizable()
.scaledToFit()
.foregroundColor(UnifiedColor.blue)
.frame(width: 20, height: 20)
}.padding()
Spacer()
Button {
print("fav")
} label: {
Image(systemName: "ellipsis")
.resizable()
.scaledToFit()
.frame(width: 20)
.foregroundColor(.white)
}.padding()
}
}
Text("Dove Body Care")
.foregroundColor(.black)
.font(.custom(Popins.medium.rawValue, size: 16))
HStack {
Text("$49.99")
.font(.custom(Popins.medium.rawValue, size: 12))
.foregroundColor(UnifiedColor.textBlue)
Spacer()
HStack {
Text("(286 Favorites)")
.font(.custom(Popins.regular.rawValue, size: 8))
.foregroundColor(.gray)
Image(ImageName.heart_line.rawValue)
.resizable()
.frame(width: 15, height: 15)
}
}
}
}
}
struct BusinessProductCell_Previews: PreviewProvider {
static var previews: some View {
BusinessProductCell()
.frame(width: 200, height: 250)
}
}
View that only contains text or second view for gird view
struct ProductOnlyDetailCell: View {
var body: some View {
loadView()
}
}
extension ProductOnlyDetailCell {
func loadView() -> some View {
VStack(alignment: .leading, spacing: 10) {
HStack {
Image(ImageName.productPlaceholder.rawValue)
.resizable()
.scaledToFill()
.frame(width: 24, height: 24)
.clipShape(Circle())
.overlay {
Circle().stroke(.white, lineWidth: 1)
}
Text("Anton Jr.")
.font(.custom(Popins.regular.rawValue, size: 12 ))
.foregroundColor(.black)
.lineLimit(1)
Spacer()
Button {
print("more btn")
} label: {
Image(systemName: "ellipsis")
.resizable()
.foregroundColor(.black)
.frame(width: 18, height: 4)
.padding([.trailing, .top, .bottom])
}
}
Text("DJ for night")
.font(.custom(Popins.bold.rawValue, size: 14))
.foregroundColor(.black)
Text("Lorem ipsum dolor sitamet, consectetur adipiscingelit. Lectus idcommodoegestas metusinterdum dolor.")
.multilineTextAlignment(.leading)
.font(.custom(Popins.regular.rawValue, size: 12))
.foregroundColor(.black)
.opacity(0.8)
Spacer()
HStack (spacing: 0){
Text("$15K")
.font(.custom(Popins.bold.rawValue, size: 14))
.foregroundColor(.black)
Text("/")
.font(.custom(Popins.bold.rawValue, size: 14))
.foregroundColor(.gray)
Text("Night")
.font(.custom(Popins.regular.rawValue, size: 14))
.foregroundColor(.gray)
}
Text("25 minute ago")
.font(.custom(Popins.regular.rawValue, size: 10))
.foregroundColor(.gray)
}
.padding(10)
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.gray.opacity(0.1))
)
}
}

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"])
}
}

Using TextField hides the ScrollView beneath it in VStack

This view hold a list of pdf names which when tapped open webviews of pdf links.
The view has a search bar above the list which when tapped causes the scrollview to disappear.
struct AllPdfListView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var pdfsFetcher = PDFsFetcher()
#State var searchString = ""
#State var backButtonHidden: Bool = false
#State private var width: CGFloat?
var body: some View {
GeometryReader { geo in
VStack(alignment: .leading, spacing: 1) {
HStack(alignment: .center) {
Image(systemName: "chevron.left")
Text("All PDFs")
.font(.largeTitle)
Spacer()
}
.padding(.leading)
.frame(width: geo.size.width, height: geo.size.height / 10, alignment: .leading)
.background(Color(uiColor: UIColor.systemGray4))
.onTapGesture {
self.mode.wrappedValue.dismiss()
}
HStack(alignment: .center) {
Image(systemName: "magnifyingglass")
.padding([.leading, .top, .bottom])
TextField ("Search All Documents", text: $searchString)
.textFieldStyle(PlainTextFieldStyle())
.autocapitalization(.none)
Image(systemName: "slider.horizontal.3")
.padding(.trailing)
}
.overlay(RoundedRectangle(cornerRadius: 10).stroke(.black, lineWidth: 1))
.padding([.leading, .top, .bottom])
.frame(width: geo.size.width / 1.05 )
ScrollView {
ForEach($searchString.wrappedValue == "" ? pdfsFetcher.pdfs :
pdfsFetcher.pdfs.filter({ pdf in
pdf.internalName.contains($searchString.wrappedValue.lowercased())
})
, id: \._id) { pdf in
if let parsedString = pdf.file?.split(separator: "-") {
let request = URLRequest(url: URL(string: "https://mylink/\(parsedString[1]).pdf")!)
NavigationLink(destination: WebView(request: request)
.navigationBarBackButtonHidden(backButtonHidden)
.navigationBarHidden(backButtonHidden)
.onTapGesture(perform: {
backButtonHidden.toggle()
})) {
HStack(alignment: .center) {
Image(systemName: "doc")
.padding()
.frame(width: width, alignment: .leading)
.lineLimit(1)
.alignmentGuide(.leading, computeValue: { dimension in
self.width = max(self.width ?? 0, dimension.width)
return dimension[.leading]
})
Text(pdf.internalName)
.padding()
.multilineTextAlignment(.leading)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
.padding(.leading)
}
}
}
.navigationBarHidden(true)
}
.accentColor(Color.black)
.onAppear{
pdfsFetcher.pdfs == [] ? pdfsFetcher.fetchPDFs() : nil
}
}
}
}
}
Pdf list and Searchbar.
The same view on Searchbar focus.
I would like the search string to filter the list of pdfs while maintaining the visibility of the list.
I was able to fix this by making my #ObservableObject an #EnvironmentObject in my App :
#main
struct MyApp: App {
#ObservedObject var pdfsFetcher = PDFsFetcher()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(pdfsFetcher)
}
}
}
struct AllPdfListView: View {
#EnvironmentObject var pdfsFetcher: PDFsFetcher
}