New coder here, and I am learning SwiftUI for the first time. I started an iOS coding journey this year after not having coded since 2005, when I last coded a social media website using HTML, CSS, and Cold Fusion7. I have been following tutorials for a while and cannot get past this bug that doesn’t generate any errors. I may have embedded the .toolbar in the wrong area of SwiftUI View. But the toolbar refuses to show at the bottom of the screen. See the two screenshots. One shows the toolbar, but the report title and select button are not at the top of the screen. The other is where I fixed the select/edit button, but the toolbar is missing.
import SwiftUI
import PhotosUI
struct ReportDetailView: View {
@Binding var report: Report
@State private var showPhotoLibrary = false
@State private var showCamera = false
@State private var selectedImages: [UIImage] = []
@State private var selectedImage: UIImage?
@State private var selectedImageIndex: Int?
@State private var showEditCaption = false
@State private var showEmailSheet = false
@State private var isSelecting = false
@State private var selectedPhotos = Set<Int>()
var body: some View {
VStack {
TextField("Report Title", text: Binding(
get: { report.title ?? "" },
set: { report.title = $0 }
))
.font(.largeTitle)
.padding(.top)
.background(Color.clear)
.frame(maxWidth: .infinity, alignment: .leading)
.textInputAutocapitalization(.words)
.onTapGesture {
self.dismissKeyboard()
}
.padding(.horizontal)
if report.photos.isEmpty {
Text("No Photos")
.foregroundColor(.gray)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
} else {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
ForEach(report.photos.indices, id: .self) { index in
VStack {
if let photo = report.photos[index].image {
Image(uiImage: photo)
.resizable()
.aspectRatio(contentMode: .fit)
.onTapGesture {
if isSelecting {
if selectedPhotos.contains(index) {
selectedPhotos.remove(index)
} else {
selectedPhotos.insert(index)
}
} else {
selectedImage = report.photos[index].image
selectedImageIndex = index
showEditCaption = true
}
}
.overlay(
isSelecting ?
Circle()
.stroke(selectedPhotos.contains(index) ? Color.blue : Color.clear, lineWidth: 3)
.frame(width: 30, height: 30)
.padding(5)
.background(Color.white.opacity(0.7).clipShape(Circle()))
.padding()
: nil
)
}
Text(report.photos[index].caption)
.font(.caption)
}
}
}
}
}
}
.navigationBarItems(trailing: Button(action: {
isSelecting.toggle()
selectedPhotos.removeAll()
}) {
Text(isSelecting ? "Cancel" : "Edit")
})
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
if isSelecting {
Button(action: deleteSelectedPhotos) {
Image(systemName: "trash")
.foregroundColor(.red)
}
Spacer()
}
Button(action: { showPhotoLibrary = true }) {
Image(systemName: "photo.on.rectangle.angled")
}
Spacer()
Button(action: { showCamera = true }) {
Image(systemName: "camera")
}
Spacer()
Button(action: { showEmailSheet = true }) {
Image(systemName: "envelope")
}
}
}
}
.sheet(isPresented: $showPhotoLibrary) {
UnifiedPicker(pickerType: .photoLibrary(single: false), selectedImages: $selectedImages)
.onDisappear {
for image in selectedImages {
if let imageData = image.jpegData(compressionQuality: 1.0) {
report.photos.append(Photo(imageData: imageData, caption: ""))
}
}
selectedImages.removeAll()
}
}
.sheet(isPresented: $showCamera) {
UnifiedPicker(pickerType: .camera, selectedImages: $selectedImages)
.onDisappear {
if let image = selectedImages.first, let imageData = image.jpegData(compressionQuality: 1.0) {
report.photos.append(Photo(imageData: imageData, caption: ""))
}
selectedImages.removeAll()
}
}
.sheet(isPresented: $showEditCaption) {
if let selectedIndex = selectedImageIndex {
EditCaptionView(photo: $report.photos[selectedIndex])
}
}
.sheet(isPresented: $showEmailSheet) {
if let emailData = emailData {
MailComposeViewController(mailData: emailData)
}
}
}
private func deleteSelectedPhotos() {
for index in selectedPhotos.sorted(by: >) {
report.photos.remove(at: index)
}
selectedPhotos.removeAll()
isSelecting.toggle()
}
private func dismissKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
private var emailData: Data? {
return PDFGenerator.generatePDF(for: report)
}
}
struct ReportDetailView_Previews: PreviewProvider {
@State static var report = Report(title: "Sample Report", date: Date(), photos: [])
static var previews: some View {
ReportDetailView(report: $report)
}
}
I have tried moving the .toolbar into different areas of the SwiftUI View, but all I have done is successfully crash the app and generate errors. This code is as close as I have been able to get.
2
Answers
I figured it out. I needed to wrap the entire VStack in a NavigationView.
Wrapping your entire VStack in a NavigationStack should also fix it. NavigationView is depreciated as of recent.