How to set max width of searchview inside toolbar [SwiftUI] - swift

I am new to SwiftUI and I trying to set maxwidth of my searchbar inside of my toolbar but the .frame(maxWidth: .infinity) won't work on my case. How can I fix this?
You can see the problem by bellow attached image, Screenshot
// this is my toolbar
My ToolbarView
NavigationView {
VStack {
contentView
}
.padding(.top, 10)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if (hideTitleAndSearchButton) {
SearchBarView(
searchText: $viewModel.personViewModel.searchText,
hideTitleAndSearchButton: Binding(projectedValue: $hideTitleAndSearchButton),
viewModel: viewModel.personViewModel
)
.frame(maxWidth: .infinity)
.animationDisable()
}
else {
Text(title)
.font(.system(size: 32))
.bold()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
if (!hideTitleAndSearchButton) {
if (viewModel.setSearchButtonVisibility) {
Image(systemName: "magnifyingglass")
.onTapGesture {
self.hideTitleAndSearchButton = true
}
.frame(width: 45, height: 45)
.foregroundColor(Color.white)
.background(Color.mPurple)
.clipShape(Circle())
.animationDisable()
}
}
}
}
}
.frame(maxWidth: .infinity)
// this is my searchview
My Searchview
var body: some View {
HStack {
Image("back_icon")
.resizable()
.renderingMode(.template)
.foregroundColor(.mPurple)
.onTapGesture {
self.isEditing = false
self.searchText = ""
self.hideTitleAndSearchButton = false
viewModel.isSearchMode = false
viewModel.isLoading = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
viewModel.personPopularList.removeAll()
viewModel.getPersonPopular()
}
}
.frame(width: 40, height: 40)
.animationDisable()
TextField("Search ...", text: $searchText)
.padding(7)
.padding(.horizontal, 20)
.background(Color(.systemGray6))
.cornerRadius(8)
.accentColor(.mPurple)
.frame(maxWidth: .infinity)
.onTapGesture {
self.isEditing = true
viewModel.isSearchMode = true
}.onChange(of: searchText) { searchItem in
if (!searchItem.isEmpty) {
viewModel.searchText = searchItem.lowercased()
let _ = print("searchItem true")
} else {
viewModel.isSearchMode = false
viewModel.personPopularList.removeAll()
viewModel.getPersonPopular()
let _ = print("searchItem false")
}
let _ = print("searchItemX: \(searchText)")
}
.animationDisable()
.padding(.trailing, 10)
}
}
thanks for anyone can help.

Add your SearchView in principal placement and don't need to set any width.
ToolbarItem(placement: hideTitleAndSearchButton ? .principal : .navigationBarLeading) {
if (hideTitleAndSearchButton) {
SearchBarView(
searchText: $viewModel.personViewModel.searchText,
hideTitleAndSearchButton: Binding(projectedValue: $hideTitleAndSearchButton),
viewModel: viewModel.personViewModel
)
.animationDisable()
}
else {
Text(title)
.font(.system(size: 32))
.bold()
}
}

Related

Construct View Like Pedantix SwiftUI

The problem is quite simple. I want to build something like Pedantix https://cemantix.certitudes.org/pedantix in SwiftUI.
I've this already :
So, I try to have my RoundedRectangle overlay to totally hide my text. And I want blocks to go at the line if needed, etc. I tried LazyHGrid (actually this), LazyVGrid, custom grid. But no results ...
import SwiftUI
struct Word: Identifiable, Equatable {
var id = UUID()
var text: String
var isFramed: Bool
var isTouched: Bool
}
struct ContentView: View {
#EnvironmentObject var service: Service
let rows = [
GridItem(.adaptive(minimum: 30)),
]
var body: some View {
GeometryReader { gr in
ScrollView {
VStack(alignment: .leading) {
HStack {
Spacer()
Image(systemName: "arrow.counterclockwise.circle")
.resizable()
.scaledToFit()
.frame(width: 24)
.onTapGesture {
service.loadRandomMovies(page: 1, completion: { _ in
service.loadMovie(id: service.randomMovieId ?? 0, completion: { _ in })
service.loadCredits(id: service.randomMovieId ?? 0, completion: { _ in })
})
}
}
HStack {
VStack {
RoundedRectangle(cornerRadius: 8)
.frame(width: 150, height: 250)
}
.padding()
VStack(alignment: .center) {
customTextView(with: service.frame(in: .title))
.padding(.bottom, 8)
customTextView(with: service.frame(in: .genres))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.movie?.releaseDate ?? "")")
.font(.title3)
.padding(.bottom, 8)
if service.movie?.tagline != "" {
Text("\"\(service.movie?.tagline ?? "")\"")
.font(.title3)
.padding(.bottom, 8)
.frame(alignment: .center)
}
customTextView(with: service.frame(in: .overview))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.credits?.cast.map({ $0.name }).joined(separator: " - ") ?? "")")
.fontWeight(.bold)
}
}
}
.padding()
}
.frame(width: gr.size.width)
}
}
}
extension ContentView {
#ViewBuilder
func customTextView(with words: [Word]) -> some View {
VStack {
LazyHGrid(rows: rows, spacing: 2) {
ForEach(words) { word -> AnyView in
if word.isFramed {
return AnyView(
Text("\(word.text)")
.padding(2)
.overlay(RoundedRectangle(cornerRadius: 4))
.overlay {
if word.isTouched {
Text("\(word.text.count)")
.foregroundColor(Color.cyan)
}
}
)
}
return AnyView(Text(word.text))
}
}
}
}
}
Do you think you could post your code so that we can see what you have done?

Can’t get the sum of ForEach items

I'm trying to display the total of the Depot Values. One depot can have many stocks.
My Overview Page has a List of Depots and should have a Text with the total value of all depots.
I get the value of one depot as following:
(connected in One-To-Many-Relationship)
let sum = depot.aktienArray.map { $0.a_purchValue }.reduce(0, +)
Portfolio View:
struct Portfolio: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Depot.d_name, ascending: true)],
animation: .default)
private var depots: FetchedResults<Depot>
// General States
#State private var showAddDepotView: Bool = false
// #State private var //left header button
#State private var isShowingConfirmation: Bool = false
#State private var isShowingMenu: Bool = false
#State private var navigateTo: AnyView?
#State private var isNavActive = false
#State private var depotToDelete: Depot?
#State var PortVal: Double = 0
#State var sum: Double = 0
var body: some View {
NavigationView {
GeometryReader { g in
VStack(spacing: 0) {
//MARK: DEPOT OVERVIEW
VStack {
VStack(alignment: .leading) {
HStack {
Text("Portfolio Value")
.font(.system(size: g.size.width / 22))
.foregroundColor(Color.gray)
Spacer()
Text("Details")
.foregroundColor(Color.white)
.font(.system(size: g.size.width / 22))
Image(systemName: "chevron.right")
.foregroundColor(Color.white)
.padding(.leading, g.size.width / -100)
}
.padding(.horizontal, g.size.width / 40)
.padding(.bottom, g.size.height / -45)
.padding(.top, g.size.height / 100)
HStack(alignment: .top) {
Text("\(PortVal as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.blue)
.minimumScaleFactor(0.4)
.font(.system(size: g.size.width / 10))
.lineLimit(1)
Text("+22,3 %")
.bold()
.foregroundColor(Color.green)
.font(.system(size: g.size.width / 20))
}
.padding(.horizontal, g.size.width / 40)
.padding(.bottom, g.size.height / 800)
HStack {
VStack(alignment: .leading) {
Text("Liquidität")
.font(.system(size: g.size.width / 22))
.foregroundColor(Color.gray)
.padding(.bottom, g.size.height / -75)
Text("339.830,87 €")
.bold()
.foregroundColor(Color.blue)
.minimumScaleFactor(0.4)
.font(.system(size: g.size.width / 14))
.lineLimit(1)
}
Spacer()
VStack(alignment: .center) {
Text("Yield")
.font(.system(size: g.size.width / 22))
.foregroundColor(Color.gray)
.padding(.bottom, g.size.height / -75)
Text("5,80 %")
.bold()
.foregroundColor(Color.blue)
.font(.system(size: g.size.width / 14))
}
Spacer()
}
.padding(.leading, g.size.width / 40)
.padding(.bottom, g.size.height / 120)
}
.frame(height: UIScreen.main.bounds.height / 5.5)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white.opacity(0.1)))
}
.padding(.horizontal, g.size.width / 30)
.padding(.vertical, g.size.height / 65)
.padding(.bottom, g.size.height / 150)
//
//MARK: DEPOT LIST
// DEPOT LIST TITLE
VStack(spacing: 0) {
Divider()
HStack {
Text("Depots")
.bold()
.font(.system(size: g.size.width / 12))
.foregroundColor(.white)
Spacer()
}
.padding(.horizontal, g.size.width / 30)
.padding(.top, g.size.width / 40)
.padding(.bottom, g.size.width / 100)
Divider()
.overlay(.white)
.padding(.horizontal, g.size.width / 30)
}
//DEPOT LIST
VStack(alignment: .leading) {
ScrollView(showsIndicators: false) {
Color.clear
.padding(.bottom, g.size.height / -200)
ForEach(depots) { depot in
let sum = depot.aktienArray.map { $0.a_purchValue }.reduce(0, +)
ZStack {
NavigationLink(destination: DepotDetail(depot: depot)) {
VStack(alignment: .leading, spacing: 6) {
HStack {
VStack (alignment: .leading){
HStack (alignment: .bottom) {
Text(depot.d_name ?? "")
.font(.system(size: 22))
.foregroundColor(Color.GrayD)
.bold()
Text("Depot-Nr.: \(depot.d_nr as NSNumber, formatter: formatter2)")
.foregroundColor(.gray)
.font(.system(size: 16))
Spacer()
}
Text("Eröffnet: \(depot.d_createDate ?? Date(), formatter: formatterD)")
.font(.system(size: 15))
.foregroundColor(.GrayD)
}
}.padding(.bottom, -0.5)
.padding(.top, 40)
HStack {
VStack (alignment: .leading) {
Text("Depot Value")
.font(.system(size: 18))
.foregroundColor(Color.gray)
if sum == 0 {
Text("0,00 €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
} else {
Text("\(sum as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
}
Text("Performance")
.font(.system(size: 18))
.foregroundColor(Color.gray)
HStack {
Text("360,00 €")
.bold()
.foregroundColor(Color.green)
.font(.system(size: 25))
.lineLimit(1)
Text("+3,6 %")
.bold()
.foregroundColor(Color.green)
.font(.system(size: 18))
}
}
Spacer()
VStack(alignment: .leading) {
Text("Yield")
.font(.system(size: 18))
.foregroundColor(Color.gray)
Text("5,80 %")
.bold()
.foregroundColor(Color.GrayD)
.font(.system(size: 25))
Spacer()
Text("FSA")
.font(.system(size: 18))
.foregroundColor(Color.gray)
Text("\(depot.d_fsa as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.GrayD)
.font(.system(size: 25))
}
}
Spacer()
Spacer()
Spacer()
}
.padding(.horizontal, g.size.width / 40)
.frame(height: UIScreen.main.bounds.height / 4.7)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white))
}.buttonStyle(FlatLinkStyle())
Menu {
Button {
self.navigateTo = AnyView(EditDepot(depot: depot))
self.isNavActive = true
} label: {
Label("Bearbeiten", systemImage: "pencil")
}
Button(role: .destructive) {
depotToDelete = depot
isShowingConfirmation = true
} label: {
Label("Löschen", systemImage: "trash")
}
} label: {
Image(systemName: "ellipsis.circle").foregroundColor(.GrayD)
}
.padding(.bottom, g.size.height / 5.75)
.padding(.leading, g.size.width / 1.25)
.confirmationDialog("Depot", isPresented: $isShowingConfirmation, titleVisibility: .visible) {
if let depot = depotToDelete {
Text("Depot \"\(depot.d_name ?? "")\" wirklich löschen?")
}
Button(role: .destructive) {
if let depot = depotToDelete {
deleteDepot(depot: depot)
}
} label: {
if let depot = depotToDelete {
Text("\"\(depot.d_name ?? "")\" wirklich löschen?")
}
}
}
.background(
NavigationLink(destination: self.navigateTo, isActive: $isNavActive) {
EmptyView()
}
)
}
.onAppear {
PortVal = 0
PortVal + sum
}
// .onChange(of: sum, perform: { _ in
// PortVal = 0
// PortVal += sum
//
// })
}
// DEPOT ADD BUTTON
VStack {
HStack {
Button {
showAddDepotView.toggle()
} label: {
HStack {
Image(systemName: "plus")
Text("Depot hinzufügen")
.font(.headline)
}
.foregroundColor(.white)
}
}
.frame(height: UIScreen.main.bounds.height / 20)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white.opacity(0.1)))
}
.padding(.top, g.size.height / 50)
}
}
.padding(.horizontal, g.size.width / 30)
}
.background(Color.GrayD.ignoresSafeArea())
.fullScreenCover(isPresented: $showAddDepotView, content: AddDepot.init)
.preferredColorScheme(.dark)
}
.navigationTitle("")
.navigationBarHidden(true)
}
}
private func deleteDepot(depot: Depot) {
withAnimation {
viewContext.delete(depot)
do {
try viewContext.save()
} catch {
print(error)
}
}
}
}
DepotDetail View:
import SwiftUI
struct DepotDetail: View {
#Environment(\.managedObjectContext) private var viewContext
#Environment(\.presentationMode) var presentationMode
// CoreData States
#StateObject var depot: Depot
#State private var aktieToDelete: AktieKauf?
#State private var a_name: String = ""
#State private var a_industry: String = ""
#State private var a_segment: String = ""
#State private var a_shares: Double = 0
#State private var a_purchPrice: Double = 0
#State private var a_purchValue: Double = 0
#State private var a_expDividend: Double = 0
#State private var a_fees: Double = 0
#State private var a_ertrag: Double = 0
#State private var a_purchDate: Date = Date()
// General States
#State private var showAddStockView: Bool = false
#State private var isShowingConfirmation: Bool = false
#State private var navigateTo: AnyView?
#State private var isNavActive = false
var body: some View {
GeometryReader { g in
VStack(spacing: 0) {
Text("\(PortfolioValue() as NSNumber, formatter: formatter) €")
// Image(systemName: "ellipsis")
//MARK: BESTAND
VStack(alignment: .leading) {
ScrollView(showsIndicators: false) {
Color.clear
.padding(.bottom, g.size.height / -200)
ForEach(depot.aktienArray) { aktie in
ZStack {
//NavigationLink(destination: StockDetail(depot: depot)) {
VStack(alignment: .leading, spacing: 6) {
HStack {
VStack (alignment: .leading){
HStack (alignment: .bottom) {
Text(aktie.unwrappedName)
.font(.system(size: 22))
.foregroundColor(Color.GrayD)
.bold()
Text("\(aktie.a_purchValue)")
.foregroundColor(.black)
Text(aktie.a_industry ?? "").foregroundColor(.black)
Spacer()
}
Text("Gekauft: \(aktie.a_purchDate ?? Date(), style: .date)")
.foregroundColor(.GrayD)
}
}
.padding(.bottom, -0.5)
.padding(.top, 40)
HStack {
VStack (alignment: .leading) {
Text("Depot Value")
.foregroundColor(Color.gray)
Text("360.000,00 €")
.bold()
.font(.system(size: g.size.width / 4))
.lineLimit(1)
}
Spacer()
VStack(alignment: .center) {
Text("Yield")
.font(.system(size: 18))
.foregroundColor(Color.gray)
Text("5,80 %")
.bold()
.foregroundColor(Color.GrayD)
}
}
HStack {
VStack(alignment: .leading) {
Text("Performance")
.font(.system(size: 18))
HStack {
Text("360,00 €")
.bold()
.font(.system(size: 25))
.lineLimit(1)
Text("+3,6 %")
.bold()
.foregroundColor(Color.green)
}
}
}
Spacer()
Spacer()
Spacer()
}
.padding(.horizontal, g.size.width / 40)
.frame(height: UIScreen.main.bounds.height / 4.7)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white))
//}
//.buttonStyle(FlatLinkStyle())
Menu {
Button {
self.navigateTo = AnyView(EditDepot(depot: depot))
self.isNavActive = true
} label: {
Label("Bearbeiten", systemImage: "pencil")
}
Button(role: .destructive) {
aktieToDelete = aktie
isShowingConfirmation = true
} label: {
Label("Löschen", systemImage: "trash")
}
} label: {
Image(systemName: "ellipsis.circle").foregroundColor(.GrayD)
}
.padding(.bottom, g.size.height / 5.75)
.padding(.leading, g.size.width / 1.25)
.confirmationDialog("Aktie", isPresented: $isShowingConfirmation, titleVisibility: .visible) {
if let aktie = aktieToDelete {
Text("Aktie \"\(aktie.a_name ?? "?")\" wirklich löschen?")
}
Button(role: .destructive) {
if let aktie = aktieToDelete {
deleteStock(aktieKauf: aktie)
}
} label: {
if let aktie = aktieToDelete {
Text("\"\(aktie.a_name ?? "?")\" wirklich löschen?")
}
}
}
//.background(
//NavigationLink(destination: self.navigateTo, //isActive: $isNavActive) {
// EmptyView()
// }
//)
}
}
}
}
.padding(.horizontal, g.size.width / 30)
//
Spacer()
}
.background(Color.GrayD.ignoresSafeArea())
.fullScreenCover(isPresented: $showAddStockView) {
AddStock(depot: depot)
}
.preferredColorScheme(.dark)
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
private func addStock() {
withAnimation {
let newAktie = AktieKauf(context: viewContext)
newAktie.a_name = a_name
newAktie.a_industry = a_industry
newAktie.a_segment = a_segment
newAktie.a_shares = a_shares
newAktie.a_purchPrice = a_purchPrice
newAktie.a_purchValue = a_purchValue
newAktie.a_expDividend = a_expDividend
newAktie.a_fees = a_fees
newAktie.a_ertrag = a_ertrag
newAktie.a_purchDate = a_purchDate
depot.addToAktieKaufRel(newAktie)
PersistenceController.shared.saveContext()
}
}
private func deleteStock(aktieKauf: AktieKauf) {
withAnimation {
viewContext.delete(aktieKauf)
do {
try viewContext.save()
} catch {
print(error)
}
}
}
func deleteAktie(at offsets: IndexSet) {
withAnimation {
for index in offsets {
let aktie = depot.aktienArray[index]
viewContext.delete(aktie)
PersistenceController.shared.saveContext()
}
}
}
private func PortfolioValue() -> Double {
var portfolioValue: Double = 0
for item in depot.aktienArray {
portfolioValue += item.a_purchValue
}
return portfolioValue
}
}
With SwiftUI you want to create a ViewModel that represents something that's the formatted version of your model. Something that's as easy as possible to map to views. You don't want to be doing calculation and logic within a View's body.
So for example:
extension Depot {
func sumOfStuff() -> Double {
aktienArray.map(\.a_purchValue).reduce(0, +)
}
}
That would be your model, then your view model might be that value stringified for presentation.
extension DepotViewModel {
var presentableSumOfStuff: String {
if depot.sumOfStuff() == 0 { return "0" } else { ... }
The problem is this "normal swift code" inside your ViewBuilder block:
ForEach(depots) { depot in
let sum = depot.aktienArray.map { $0.a_purchValue }.reduce(0, +) // This should not be here
ZStack {
Within a ViewBuilder context, you can't put normal swift code, it should just be a list of Views.
If you set up your ViewModel right, then you don't have complex logic in the view like instead of this:
if sum == 0 {
Text("0,00 €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
} else {
Text("\(sum as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
}
You would have
Text($0.presentableSumOfStuff)
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)

SwiftUI How to Disable Button While Uploading Objects in an Array

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
}
}

SwiftUI button glitches on keyboard dismiss

I have a button on my view that, when pressed, calls a hideKeyboard function which is the following:
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
However, when the button is pressed and the keyboard gets dismissed, a glitch occurs where the button stays in place while the view moves downward:
https://giphy.com/gifs/Z7qCDpRSGoOb9CbVRQ
Reproducible example:
import SwiftUI
struct TestView: View {
#State private var username: String = ""
#State private var password: String = ""
#State private var isAnimating: Bool = false
var lightGrey = Color(red: 239.0/255.0,
green: 243.0/255.0,
blue: 244.0/255.0)
var body: some View {
ZStack() {
VStack() {
Spacer()
Text("Text")
.font(.title)
.fontWeight(.semibold)
.padding(.bottom, 15)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Text")
.font(.subheadline)
.padding(.bottom)
.frame(maxWidth: .infinity, alignment: .leading)
TextField("username", text: $username)
.padding()
.background(self.lightGrey)
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("password", text: $password)
.padding()
.background(self.lightGrey)
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: {
self.hideKeyboard()
login()
})
{
if isAnimating {
ProgressView()
.colorScheme(.dark)
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
else {
Text("Text")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
}
.disabled(username.isEmpty || password.isEmpty || isAnimating)
Text("Text")
.font(.footnote)
.frame(maxWidth: .infinity, alignment:.leading)
Spacer()
}
.padding()
.padding(.bottom, 150)
.background(Color.white)
}
}
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
func login() {
isAnimating = true
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
This is due to view replacement inside button, find below a fixed re-layout.
Tested with Xcode 13.2 / iOS 15.2 (slow animation was activated for demo)
Button(action: {
self.hideKeyboard()
login()
})
{
VStack { // << persistent container !!
if isAnimating {
ProgressView()
.colorScheme(.dark)
}
else {
Text("Text")
}
}
.foregroundColor(.white)
.font(.headline)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
.disabled(username.isEmpty || password.isEmpty || isAnimating)
Looks like a conflict between UIApplication.shared.sendAction and SwiftUI: hiding the keyboard starts the animation, but updating the isAnimating property resets it.
Changing the order of calls solves the problem:
Button(action: {
login()
self.hideKeyboard()
})

I'd like to use the navigation link.Newbie Wang is in the process of hair loss

I want to use Navigationlink. I've been a novice for 2 weeks since I started.I am currently learning SwiftUi.
I created "OnboredView" after watching YouTube, but I don't know how to connect "OnboredView" to "CountentView".
NavigationView(){
NavigationLink(destination: OnboardView())
I learned how to make it like this through YouTube, but I don't know what to do now. I put it here and there, but the red errors bother me.
Tell me how to connect "NavigationLink" by pressing the button on "CountentView".
I'd like to click "Chevron.Light" to move on to "OnboredView."And if possible, please let me know how I can get rid of the "onboard screen" on the second run?
I am not good at English.I'm sorry. I'm experiencing hair loss again.
import SwiftUI
struct ContentView: View {
#State private var animate: Bool = false
var body: some View {
ZStack{
ZStack{
Image("rogo1")
.resizable()
.frame(width: 75, height: 75)
.offset(y: animate ? -100 : 0)
}
ZStack{
Image("rogo2")
.resizable()
.frame(width: 75, height: 75)
.offset(y: animate ? -100 : 0)
}
VStack {
HStack {
Spacer()
Image("images (1)")
.resizable()
.frame(width: 300, height: 300)
.offset(x: animate ? 300 : 150, y: animate ? -300 : -150)
}
Spacer()
HStack {
Image("images (1)")
.resizable()
.frame(width: 400, height: 400)
.offset(x: animate ? -500 : -150, y: animate ? 500 : 150)
Spacer()
}
}
ZStack(alignment: .bottom){
GeometryReader { g in
VStack (alignment: .leading, spacing: 20){
Text("안녕하세요!")
.font(.title)
.fontWeight(.semibold)
.padding(.top, 20)
//인삿말과 회원가입
Text("기분 좋은 매일습관을 만들기 위한 앱 ( ) 입니다! 시간표와 더불어 루틴을 함께 할수
있도록 설계 되었습니다.저희 ( )와 함께 계획해봐요!")
.fontWeight(.medium)
.multilineTextAlignment(.center)//중앙으로 결집
.padding(5)
ZStack {
Button(action: {},label: {
Image(systemName: "chevron.right")
.font(.system(size:20, weight: .semibold))
.frame(width: 60, height: 60)
.foregroundColor(.black)
.background(Color.white)
.clipShape(Circle())
.overlay(
ZStack {
Circle()
.stroke(Color.black.opacity(0.04),lineWidth: 4)
Circle()
.trim(from: 0, to: 0.03)
.stroke(Color.white,lineWidth: 4)
.rotationEffect(.init(degrees: -40))
})
})
.padding(-10)
}
Spacer()
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 30)
.background(Color.green)
.clipShape(CustomShape(leftCorner: .topLeft, rightCorner: .topRight,
radii: 20))
.offset(y: animate ? g.size.height : UIScreen.main.bounds.height)
}
}.frame(height: 275)
//여기까지 짤라도 됨 온보드
}
.frame(maxWidth: .infinity)
.edgesIgnoringSafeArea(.all)
.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
withAnimation(Animation.easeOut(duration: 0.45)){
animate.toggle()
}
}
})
}
{
struct CustomShape: Shape {
var leftCorner: UIRectCorner
var rightCorner: UIRectCorner
var radii: CGFloat
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners:
[leftCorner,rightCorner], cornerRadii: CGSize(width: radii, height: radii))
return Path(path.cgPath)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
Group {
}
}
}
}
import SwiftUI
struct OnboardView: View {
#AppStorage("currentPage") var currentPage = 1
var body: some View {
if currentPage > totalPages {
Home()
}else{
WalkthroughScreen()
}
}
}
struct OnboardView_Previews: PreviewProvider {
static var previews: some View {
OnboardView()
}
}
struct Home: View {
var body: some View{
Text("welcome To Home!!!")
.font(.title)
.fontWeight(.heavy)
}
}
//..Walkthrough Screen..
struct WalkthroughScreen: View {
#AppStorage("currentPage") var currentPage = 1
var body: some View {
//For Slide Animation
ZStack{
//Changing Between Views..
if currentPage == 1 {
ScreenView(image: "image1", title: "Step1", detail: "", bgColor:
Color(.white))
//transition(.scale)영상에서는 넣었으나 오류가나서 사용하지 못함
}
if currentPage == 2 {
ScreenView(image: "image2", title: "Step2", detail: "", bgColor:
Color(.white))
}
if currentPage == 3 {
ScreenView(image: "image3", title: "Step3", detail: "아니 ㅡㅡ 이런 방법이 유레카",
bgColor: Color(.white))
}
}
.overlay(
Button(action: {
//changing views
withAnimation(.easeInOut){
if currentPage < totalPages {
currentPage += 1
}else{
currentPage = 1
//For app testing ONly
}
}
}, label: {
Image(systemName: "chevron.right")
.font(.system(size: 20, weight: .semibold))
.foregroundColor(.black)
.frame(width: 60, height: 60)
.clipShape(Circle())
//strclulat Slider
.overlay(
ZStack{
Circle()
.stroke(Color.black.opacity(0.04),lineWidth: 4
Circle()
.trim(from: 0, to: CGFloat(currentPage) /
CGFloat(totalPages))
.stroke(Color.green,lineWidth: 4)
.rotationEffect(.init(degrees: -99))
}
.padding(-15)
)
})
.padding(.bottom,20)
,alignment: .bottom
)
}
}
struct ScreenView: View {
var image: String
var title: String
var detail: String
var bgColor: Color
#AppStorage("currentPage") var currentPage = 1
var body: some View {
VStack(spacing:20){
HStack {
//Showing it only for first page..
if currentPage == 1{
Text("Hello Members!")
.font(.title)
.fontWeight(.semibold)
//Letter Spacing
.kerning(1.4)
}else{
//Back Butten..
Button(action: {
withAnimation(.easeInOut){
currentPage -= 1
}
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.white)
.padding(.vertical,10)
.padding(.horizontal)
.background(Color.black.opacity(0.4))
.cornerRadius(10)
})
}
Spacer()
Button(action: {
withAnimation(.easeInOut){
currentPage = 4
}
}, label: {
Text("Skip")//글자입력
.fontWeight(.semibold)//글자 폰트변경
.kerning(1.2)//글자간 간격 조정
})
}
.foregroundColor(.black)//그라운드 컬러 변경
.padding()
Spacer(minLength: 0)//수평,수직 줄바꿈
Image(image)//이미지 삽입
.resizable()//크기 확대
.aspectRatio(contentMode: .fit)//이미지 크기
Text(title)
.font(.title)//폰트 크기변경
.fontWeight(.bold)//폰트 두께 변경
.foregroundColor(.black)//색깔 변경
.padding(.top)
//Change with your Own Thing..
Text(detail)
.fontWeight(.semibold)
.kerning(1.3)//자간조정
.multilineTextAlignment(.center)//텍스트를 중앙으로 결집
Spacer(minLength: 220)//minimun Spacing When phone is reducing수직위치 조정
}
.background(bgColor.cornerRadius(10).ignoresSafeArea())
}
}
var totalPages = 3