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
}
Related
I have a SwiftUI form where the user adds images to an array from a picker and then a function fires to upload each image. The images are displayed in a ForEach as either an upload spinner while the upload is happening OR the actual image once the upload has completed.
I'd like the NEXT button, which would remove the user from the view, to be disabled until all of the uploads have completed.
I'm not sure how to inform the State of the Parent View that all of the uploads are completed.
Here's what my Parent View looks like:
struct ProjectFormStep4: View {
#EnvironmentObject var authVM: AuthorizationClass
#EnvironmentObject var projectVM: ProjectsViewModel
#EnvironmentObject var uploadVM: UploadManager
#ObservedObject var mediaItems = PickedMediaItems()
#State private var showSheet: Bool = false
#State private var showUploadView: Bool = false
var body: some View {
ZStack{
Color("BrandGrey").ignoresSafeArea()
VStack{
HStack{
Button{
projectVM.showProjectFormStep1 = false
projectVM.showProjectFormStep2 = false
projectVM.showProjectFormStep3 = true
projectVM.showProjectFormStep4 = false
} label: {
Text("< Back")
.font(.headline)
.foregroundColor(Color("BrandLightBlue"))
}
Spacer()
}
Spacer()
.frame(height: 30)
ZStack{
Rectangle()
.fill(Color(.white))
.frame(width: 300, height: 100)
.cornerRadius(12)
Image(systemName: "camera")
.resizable()
.foregroundColor(Color("BrandLightBlue"))
.scaledToFit()
.frame(height: 60)
}.onTapGesture {
self.showSheet = true
}
Spacer()
.sheet(isPresented: $showSheet, content: {
PhotoPicker(mediaItems: mediaItems) { didSelectItem in
showUploadView = true
showSheet = false
}
})
Spacer()
ScrollView{
ForEach(uploadVM.uploadedMedia, id:\.id){ item in
ImageArea(
item: item,
items: uploadVM.uploadedMedia
)
}
}
Spacer()
if showUploadView {
ForEach(mediaItems.items, id: \.id) { item in
UploadView(item: item)
}
}
Button {
} label: {
Text("Next")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color("BrandOrange"))
.cornerRadius(15.0)
}
}.padding()
}
}
}
Here's my Child View of which handles the upload spinner and actual image:
struct ImageArea: View {
#EnvironmentObject var projectVM: ProjectsViewModel
#EnvironmentObject var uploadVM: UploadManager
#State private var showThumbnailOptions: Bool = false
var item: UploadedMedia
var items : [UploadedMedia]
var body: some View {
if item.secureUrl == "" {
ZStack{
UploadSpinner()
.frame(
width: 300,
height: 200
)
.cornerRadius(12)
VStack(alignment: .leading ){
HStack{
Spacer()
.frame(
width: 30
)
Image(systemName: self.getWaterMarkName(item: item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.padding(4)
.background(Color.black.opacity(0.5))
.foregroundColor(.white)
}
Spacer()
HStack{
Spacer()
Text("\(item.uploadProgress)%")
.foregroundColor(.black)
.font(.body)
.padding()
Spacer()
}
}
}
} else {
ZStack{
ProjectRemoteUploadImage(projectImageUrl: projectVM.standardizeThumbnail(thumbnailUrl: item.secureUrl))
.aspectRatio(contentMode: .fit)
.onTapGesture {
showThumbnailOptions.toggle()
}
if showThumbnailOptions {
Color(.black).opacity(0.8)
VStack{
Spacer()
HStack{
Spacer()
Image(systemName: "arrowshape.turn.up.backward.circle.fill")
.resizable()
.foregroundColor(.white)
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
self.showThumbnailOptions.toggle()
}
Image(systemName: "trash.circle.fill")
.resizable()
.foregroundColor(.red)
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
deleteThumbnail(
item: item
)
}
Spacer()
}
Spacer()
}
}
}
}
}
func getWaterMarkName(item: UploadedMedia) -> String {
if item.mediaType == "photo"{
return "photo"
} else if item.mediaType == "video" {
return "video"
} else {
return "photo"
}
}
func deleteThumbnail(item: UploadedMedia){
let itemID = item.id
guard let itemIndex = uploadVM.uploadedMedia.firstIndex(where: {$0.id == itemID}) else { return }
uploadVM.uploadedMedia.remove(at: itemIndex)
}
}
Adding model info for the uploadedMedia
struct UploadedMedia {
var id: String
var uploadProgress: Float
var secureUrl: String
var mediaType: String
var width: Int
var length: Double
init(
id: String,
uploadProgress: Float,
secureUrl: String,
mediaType: String,
width: Int,
length: Double
){
self.id = id
self.uploadProgress = uploadProgress
self.secureUrl = secureUrl
self.mediaType = mediaType
self.width = width
self.length = length
}
}
I am trying to create a hamburger menu that when you click the "hamburger" (three horizontal lines) button, the menu will slide out. I am following the tutorial found here, but the only thing that isn't working is the lines for the Hamburger image is not showing up on my application. Everything else works, but for some reason this is the one thing that is not working.
Here is my code for the ContentView, where it hosts the problem code
struct ContentView: View {
#State var showMenu = false
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
return NavigationView {
GeometryReader { geometry in
ZStack(alignment: .leading) {
MainView(showMenu: self.$showMenu)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.showMenu ? geometry.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
if self.showMenu {
MenuView()
.frame(width: geometry.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag)
}
.navigationBarTitle("Side Menu", displayMode: .inline) // this works
//somewhere below here is the problem
.navigationBarItems(leading: (
Button(action: {
withAnimation {
self.showMenu.toggle()
}
}) {
Image(systemName: "three_horizontal_lines")
.imageScale(.large)
}
))
}
}
}
Here is the MainView:
struct MainView: View{
#Binding var showMenu: Bool
var body: some View{
Button(action: {
withAnimation{
self.showMenu = true
}
}){
Text("Show Menu")
}
}
}
Lastly, this is the MenuView:
struct MenuView: View{
var body: some View{
VStack(alignment: .leading){
HStack{
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Profile")
.foregroundColor(.gray)
.font(.headline)
}
.padding(.top, 100)
HStack{
Image(systemName: "envelope")
.foregroundColor(.gray)
.imageScale(.large)
Text("Messages")
.foregroundColor(.gray)
.font(.headline)
}
.padding(.top, 30)
HStack{
Image(systemName: "gear")
.foregroundColor(.gray)
.imageScale(.large)
Text("Settings")
.foregroundColor(.gray)
.font(.headline)
}
.padding(.top, 30)
Spacer()
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
}
}
I have checked for any misspellings and even copied and pasted the code from the original tutorial, but it seems nothing allows me to see the burger image like is shown on the tutorial. Any thoughts on what else I could try?
I have a list view embedded in a Navigation View, however, this Navigation View is only about the screen height. This list links to a detailed view, and when a row is tapped, the Detail View only takes up half of the screen. I would like it to open a completely new window.
Screenshots:
The code is used is the following:
import SwiftUI
import CoreData
extension UIScreen{
static let screenWidth = UIScreen.main.bounds.size.width
static let screenHeight = UIScreen.main.bounds.size.height
static let screenSize = UIScreen.main.bounds.size
}
let topCardHeight: CGFloat = 350
struct HomeView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: SavedPoem.entity(), sortDescriptors: []) var savedpoems : FetchedResults<SavedPoem>
var body: some View {
VStack {
VStack (alignment: .center){
Text("Today's Poem, November 18th...")
.font(.subheadline)
.foregroundColor(.white)
.padding(.bottom)
.padding(.top, 75)
Text("No Man Is An Island")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
.padding(.bottom,1)
Text("by John Donne")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
.padding(.bottom, 35)
Button(action: {}) {
Text("Read Now")
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.font(.subheadline)
.foregroundColor(.white)
.padding(15)
.border(Color.white, width: 3)
}
}
.frame(width: UIScreen.screenWidth, height: topCardHeight, alignment: .top)
.background(Color.black)
.edgesIgnoringSafeArea(.top)
.edgesIgnoringSafeArea(.bottom)
.padding(.bottom, 0)
NavigationView{
List{
ForEach(savedpoems, id:\.title) {SavedPoem in
NavigationLink (destination: ContentView()){
ZStack {
Rectangle().fill(Color.white)
.frame(width: UIScreen.main.bounds.width - 32, height: 70)
.cornerRadius(10).shadow(color: .gray, radius: 4)
HStack {
VStack (alignment: .leading){
Text("\(SavedPoem.title ?? "")").font(.headline)
.lineLimit(1)
Text("\(SavedPoem.author ?? "")").font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
}.padding()
}
// }.onDelete(perform: remove)
}
}
}
.navigationTitle("My Saved Poems")
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.top)
.padding(.top, 0)
}
}
// func remove(at offsets : IndexSet) {
// for index in offsets {
// let delete = SavedPoem[index]
// self.moc.delete(delete)
// }
// try? self.moc.save()
// }
}
Any ideas? Thanks in advance.
If you need the same UI:
(The navigation view at the bottom of your top view) , here is a solution for it .
var body: some View {
#EnvironmentObject var sharedViewModel : SharedViewModel
VStack {
VStack (alignment: .center){
if sharedViewModel.currentPageIsHome {
// your top view body here ..
}
}
NavigationView{\*....*\}
}
}.onAppear {
sharedViewModel.currentPageIsHome = true
}.onDisappear {
sharedViewModel.currentPageIsHome = false
}
And you need to create an Observable object
class SharedViewModel: ObservableObject {
#Published var currentPageIsHome = false
}
And don't forget to initialize it in your SceneDelegate
ContentView().environmentObject(SharedViewModel())
Or
Clear version :
change your view hierarchy to :
NavigationView {
List{
Section(header: YourTopView()) {
// ... your list content
}
}
}
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
I wish to add a 'trash' image on the top-right side of each button when 'Delete Button' is pressed, so that when user hits the trash image, the button will be removed from the vstack.
I think I should use zstack to position the trash image but I don't know how for now.
Below shows where the trash image should be located in each button.
Also, when I press the 'Delete Button', it seems that each button's text size and spacing with another button is changed slightly. How do I overcome this problem? The button position, spacing, textsize should be unchanged when 'Delete Button' is hit.
struct someButton: View {
#Environment(\.editMode) var mode
#ObservedObject var someData = SomeData()
#State var newButtonTitle = ""
#State var isEdit = false
var body: some View {
NavigationView{
// List{ // VStack
VStack{
VStack{
ForEach(Array(someData.buttonTitles.keys.enumerated()), id: \.element){ ind, buttonKeyName in
//
Button(action: {
self.someData.buttonTitles[buttonKeyName] = !self.someData.buttonTitles[buttonKeyName]!
print("Button pressed! buttonKeyName is: \(buttonKeyName) Index is \(ind)")
print("bool is \(self.someData.buttonTitles[buttonKeyName]!)")
}) {
HStack{ //HStack, ZStack
if self.isEdit{
Image(systemName: "trash")
.foregroundColor(.red)
.onTapGesture{
print("buttonkey \(buttonKeyName) will be deleted")
self.deleteItem(ind: ind)
}
}
Text(buttonKeyName)
// .fontWeight(.semibold)
// .font(.title)
}
}
.buttonStyle(GradientBackgroundStyle(isTapped: self.someData.buttonTitles[buttonKeyName]!))
.padding(.bottom, 20)
}
}
HStack{
TextField("Enter new button name", text: $newButtonTitle){
self.someData.buttonTitles[self.newButtonTitle] = false
self.newButtonTitle = ""
}
}
}
.navigationBarItems(leading: Button(action: {self.isEdit.toggle()}){Text("Delete Button")},
trailing: EditButton())
// .navigationBarItems(leading: Button(action: {}){Text("ergheh")})
// }
}
}
func deleteItem(ind: Int) {
let key = Array(someData.buttonTitles.keys)[ind]
print(" deleting ind \(ind), key: \(key)")
self.someData.buttonTitles.removeValue(forKey: key)
}
}
struct GradientBackgroundStyle: ButtonStyle {
var isTapped: Bool
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.frame(maxWidth: .infinity, maxHeight: 50)
.padding()
.foregroundColor(isTapped ? Color.blue : Color.black)
.background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(40)
.overlay(RoundedRectangle(cornerRadius: 40)
.stroke(isTapped ? Color.blue : Color.black, lineWidth: 4))
.shadow(radius: 40)
.padding(.horizontal, 20)
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
//
}
}
class SomeData: ObservableObject{
#Published var buttonTitles: [String: Bool] = ["tag1": false, "tag2": false]
}
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4 (with some replicated code)
var body: some View {
Button(action: { }) {
Text("Name")
}
.buttonStyle(GradientBackgroundStyle(isTapped: tapped))
.overlay(Group {
if self.isEdit {
ZStack {
Button(action: {print(">> Trash Tapped")}) {
Image(systemName: "trash")
.foregroundColor(.red).font(.title)
}.padding(.trailing, 40)
.alignmentGuide(.top) { $0[.bottom] }
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
}
})
.padding(.bottom, 20)
}