SwiftUI - NavigationLink' content is not readable when contextMenu is presented - ios

I have a coloured NavigationLink that has context-menu. Its content is not readable when the context-menu is presened. I have epxreminted using the context-menu on the immediate sub-view of the NavigationLink, but it is stil the same issue.
NavigationLink(destination: Text("View")) {
VStack(alignment: .leading) {
Text("Context Menu")
.font(.system(size: 24, weight: .bold))
}
.frame(minWidth: 0, maxWidth: .infinity, idealHeight: 70)
.foregroundColor(.white)
.padding()
.cornerRadius(3.0)
}
.background(Color.red)
.contextMenu {
Section {
Button(action: {
}) {
Label("Edit", systemImage: "square.and.pencil")
}
}
Section(header: Text("Secondary actions")) {
Button(action: {}) {
Label("Delete", systemImage: "trash")
}
}
}
NavigatoinLinks look like in its original state.
When the context-menu is presented. The problem is even worse If I use small sized text.
I have tested on ios 14.2 both on simulator and physical device.
Info
Hierarchy of views.
ScrollView {
LazyVStack {
ForEach(data) { item in
// NavigationLink
}
}
}
Update
This is a similar project that has the same issue.
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView {
LazyVStack {
ForEach(0..<10) { item in
NavigationLink(destination: Text("View")) {
VStack(alignment: .leading) {
Text("Context Menu")
.font(.system(size: 24, weight: .bold))
}
.frame(minWidth: 0, maxWidth: .infinity, idealHeight: 70)
.foregroundColor(.white)
.padding()
.cornerRadius(3.0)
}
.background(Color.red)
.contextMenu {
Section {
Button(action: {
}) {
Label("Edit", systemImage: "square.and.pencil")
}
}
Section(header: Text("Secondary actions")) {
Button(action: {}) {
Label("Delete", systemImage: "trash")
}
}
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

I know it's been two years, but this still remains an issue with iOS 16 when you use LazyVStack. In contrast with List, SwiftUI generates a clear automatic preview. What's new with iOS 16 is that you can now define a custom preview, and SwiftUI will present that preview without the blur. If you choose to use LazyVStack for performance or another reason, this gives you an alternative, albeit with duplicate code.
From your example above, you would add:
NavigationLink(destination: Text("View")) {
// View such as your VStack containing Text
}
.contextMenu {
// Menu items
} preview: {
// View, again, but you might want to simplify or modify
}

Related

SwiftUI add `menu` in `.safeAreaInset` cause strange layout issue

Here is the sample project source code: sample code
import SwiftUI
struct TestMenuInSafeAreaInset: View {
#State private var message = ""
var body: some View {
VStack {
Rectangle()
.fill(Color.blue)
}
.safeAreaInset(edge: .bottom) {
HStack {
TextField("Input your message", text: $message)
.padding()
.background(Color.brown)
.cornerRadius(12)
.foregroundColor(.white)
Menu {
Button {
} label: {
Text("Confirm")
}
Button {
} label: {
Text("Cancel")
}
} label: {
Image(systemName: "square.and.arrow.up.fill")
.tint(.white)
.padding()
.background(Color.brown)
.cornerRadius(50)
}
}
.padding()
}
}
}
struct TestMenuInSafeAreaInset_Previews: PreviewProvider {
static var previews: some View {
TestMenuInSafeAreaInset()
}
}
When keyboard appear, the action menu button (right-bottom corner) shift up a little, and tap the menu will cause strange layout as the gif shown.
I think this is a bug, any solution to fix it? I test above code with iOS 16 and iOS 15 with same behaviour.
[Updated 2022.10.10 11:31 +8]
As #Saket Kumar 's solution, I update the code as below, the issue is reproduced even I give size to the menu.
Test with iPhone 14 pro simulator iOS 16.
struct TestMenuInSafeAreaInset: View {
#State private var message = ""
var body: some View {
VStack {
TextField("Input your user name", text: $message)
.padding()
.background(Color.gray.opacity(0.3))
.cornerRadius(12)
.padding()
Spacer()
}
.safeAreaInset(edge: .bottom) {
HStack {
Spacer()
.frame(height: 70)
Menu {
Button {
} label: {
Text("Confirm")
}
Button {
} label: {
Text("Cancel")
}
} label: {
Image(systemName: "square.and.arrow.up.fill")
.padding()
.tint(.white)
.background(Color.brown)
.cornerRadius(50)
}
.frame(width: 50, height: 50)
}
.padding(.horizontal, 20)
.background(Color.blue)
}
}
}
Seems an issue with SwiftUI. I tested your code and I was able to reproduce the issue.
I had my hunch that this weird behaviour may be caused by TextField's Frame, and Menu's frame overlapping, which SwiftUI is trying to accommodate.
So I tried giving them frame manually, and it seems to fix the issue.
Just change this portion of the code.
.safeAreaInset(edge: .bottom) {
HStack {
TextField("Input your message", text: $message)
.padding()
.background(Color.brown)
.cornerRadius(12)
.foregroundColor(.white)
.frame(width: UIScreen.main.bounds.width*0.75, height: 50, alignment: .leading)
Spacer()
Menu {
Button {
} label: {
Text("Confirm")
}
Button {
} label: {
Text("Cancel")
}
} label: {
Image(systemName: "square.and.arrow.up.fill")
.tint(.white)
.padding()
.background(Color.brown)
.cornerRadius(50)
}.frame(width: UIScreen.main.bounds.width*0.10, height: 50, alignment: .trailing)
}
.padding()
}
Fair warning. Those frames I have put may not be exactly as you want. I have tested this code It works.
[Just added your code in demo project to simulate it]
My suggestion would be to give fixed width, height to Menu say 60*60. Then take out 60 from the screen's width, account for padding. And give suitable frame to TextField.

Huge black bars on top and bottom of UI - SwiftUI

I have an issue with Views in the sample app I am trying to help me learn SwiftUI views.
The black bars at the top and bottom shouldn't be there. I want to extend the view to the top and bottom of the safe area limit.
Here is the code using Xcode 13.4.1 and iOS 15.5:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
HotelView()
.tabItem {
Image(systemName: "house")
.resizable()
Text("Home")
}
HomeView(title: "My Trips")
.tabItem {
Image(systemName: "suitcase")
Text("My Trips")
}
HomeView(title: "Saved")
.tabItem {
Image(systemName: "heart")
Text("Saved")
}
HomeView(title: "Profile")
.tabItem {
Image(systemName: "person")
Text("Profile")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Also the HotelView file:
import SwiftUI
struct HotelView: View {
var body: some View {
NavigationView {
List(hotels) { hotel in
ScrollView(.vertical, showsIndicators: true) {
NavigationLink(destination: DetailView(hotels: hotel)) {
HStack {
Image(hotel.image)
.resizable()
.frame(height: 100)
.frame(maxWidth: .infinity)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.overlay(
Text(hotel.city)
.fontWeight(.bold)
.foregroundColor(.white),alignment: .center)
}
}
}
}
// navtitle
}
}
}
And the result in the simulator is:
Black bars on top and bottom
import SwiftUI
#main
struct Mohsin_city_plannerApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
So the solution is to add launch screen to your plist info file even in SwiftUI

SwiftUI: How to force SwiftUI ContextMenu Preview to take up the full Width?

Context
I am currently working with the new SwiftUI ContextMenu, which supports a Preview. However, I have difficulties forcing the Preview to take up the full width of the screen.
Code
Text("Hello World")
.contextMenu { menuItems } preview: { Text("Preview") }
Question
Please Note: I already tried to add .frame(maxWidth: .infinity) to Text("Preview") but this didn't solve the problem.
How can I force the Preview to take up the full width of the screen?
You could wrap your preview view in a NavigationStack. That's easy, but it will not only expand the preview horizontally, it will also expand vertically, which may not be what you're looking for.
Another way would be to set the idealWidth for the preview:
import SwiftUI
struct ContentView: View {
#State var viewWidth = CGFloat.zero
var body: some View {
VStack {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Long press me")
}
.contextMenu {
Button("Action") {}
} preview: {
Text("Preview Text")
.padding(.vertical, 50)
.frame(idealWidth: viewWidth)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay {
GeometryReader { geometry in
Color.clear
.onAppear {
self.viewWidth = geometry.frame(in: .local).size.height
}
}
}
}
}
This was deprecated.
You didn't give much code, so I used random example:
You can use Menu like this:
struct MenuExample: View {
var body: some View {
Menu {
Button("Duplicate", action: duplicate)
Button {
rename()
} label: { Label("rename", systemImage: "square.and.pencil") }
Button {
delete()
} label: { Label("delete", systemImage: "trash") }
Menu {
Button("Open in Preview", action: openInPreview)
Button("Save as PDF", action: saveAsPDF)
} label: { Label("PDF", systemImage: "doc.fill") }
} label: {
Label("Menu", systemImage: "book.fill")
.font(.title)
}
.foregroundColor(.orange)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding(30)
.background(.orange.opacity(0.3))
}
func duplicate() {}
func delete() {}
func rename() {}
func saveAsPDF() {}
func openInPreview() {}
}
struct MenuTemp_Previews: PreviewProvider {
static var previews: some View {
MenuExample()
}
}

SwiftUI List/Destination Navigation Bar Title Ghosting

I'm experiencing a strange visual bug with navigation bar titles. This swiftUI app has
a basic master/detail type view with a List view and a Detail view that is presented
with a NavigationLink. That Detail view is then a list of Texts displaying information
or a list of TextFields when the page is in the edit mode.
When in the Detail view with edit mode and I scroll to the bottom item in the list and
edit it then the navigationBarTitle on the first view has a ghost of the "Detail"
navigationBarTitle of the second view. If I don't scroll the ghosting does not appear.
By scrolling, I include the scroll so the keyboard does not hide the textfield.
If I change the Detail view navigationBarTitle to .inline the issues is a nearly
imperceptible flash with no permanent residual. Obviously, I will leave it in this mode
but I'd certainly prefer a standard iOS look and feel.
I've experimentally disabled every combination that I can think of - no joy.
The root List:
NavigationView {
List {
ForEach(myPhysicians, id: \.self) { mp in
NavigationLink(destination: PhysicianDetailView(physician: mp)) {
Text(mp.lastName ?? "no last name")
}
}
.onDelete { indexSet in
//standard core data delete process
}
}.navigationBarTitle("Physicians")
.navigationBarItems(
leading:
NavigationLink(destination: AddPhysicianView()) {
Image(systemName: "plus.circle.fill")
.font(.title)
},
trailing:
EditButton()
)//bar button items
}//nav
The Detail View:
var body: some View {
DispatchQueue.main.async {
self.localFirstName = self.physician.wrappedFirstName
self.localLastName = self.physician.wrappedLastName
//bunch more...
}
return ZStack(alignment: .topTrailing) {
List {
VStack { //group 1
VStack (alignment: .leading) {
Text("First Name:")
.font(.system(size: 14))
.font(.subheadline)
if !showEditView {
Text("\(physician.wrappedFirstName)")
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 5)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.headline)
} else {
TextField("tf firstname", text: $localFirstName)
.modifier(TextFieldSetup())
}
}
VStack (alignment: .leading) {
Text("Last Name:")
.font(.system(size: 14))
.font(.subheadline)
if !showEditView {
Text("\(physician.wrappedLastName)")
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 5)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.headline)
} else {
TextField("tf lastname", text: $localLastName)
.modifier(TextFieldSetup())
}
}
//bunch more...
}//outer vstack
}//list
.frame(maxWidth: .infinity - 20)
//.navigationBarTitle("Detail",displayMode: .inline)
.navigationBarTitle("Detail")
.navigationBarBackButtonHidden(true)
.navigationBarItems(
leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
if self.showEditView {
Text("Cancel")
} else {
Image(systemName: "arrowshape.turn.up.left.circle.fill")
.font(.title)
}
},
trailing:
Button(action: {
if self.showEditView {
self.saveEditedPhysicianToCoreData(physician: self.physician)
}
self.showEditView.toggle()
}) {
Image(systemName: self.showEditView ? "gear" : "square.and.pencil")
.font(.title)
.frame(width: 60, height: 60)
}
)//nav bar items
}//zstack
.modifier(AdaptsToSoftwareKeyboard())
}//body
Any guidance would be appreciated. Xcode 11.6 iOS 13.6
Added: 29 July 2020
struct AdaptsToSoftwareKeyboard: ViewModifier {
#State var currentHeight: CGFloat = 0
func body(content: Content) -> some View {
content
.padding(.bottom, self.currentHeight)
.edgesIgnoringSafeArea(self.currentHeight == 0 ? Edge.Set() : .bottom)
.onAppear(perform: subscribeToKeyboardEvents)
}
private let keyboardWillOpen = NotificationCenter.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect }
.map { $0.height }
private let keyboardWillHide = NotificationCenter.default
.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in CGFloat.zero }
private func subscribeToKeyboardEvents() {
_ = Publishers.Merge(keyboardWillOpen, keyboardWillHide)
.subscribe(on: RunLoop.main)
.assign(to: \.self.currentHeight, on: self)
}
}

How to increase tappable area of navigationBarItem in SwiftUI?

I have this code for my view
struct ContentView: View {
var body: some View {
NavigationView{
List{
ForEach(0...5, id: \.self) { note in
VStack(alignment: .leading) {
Text("title")
Text("subtitle")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
.navigationBarItems(trailing: resetButton)
.navigationBarTitle(Text("Notes"))
}
}
var resetButton: some View {
Button(action: {
print("reset")
}) {
Image(systemName: "arrow.clockwise")
}
.background(Color.yellow)
}
}
resetButton looks like this:
When I am tapping the resetButton, it seems like only the yellow area responds to touches.
How do I make tappable area of this button bigger? (Make it behave like a normal UIBarButtonItem)
You can change the frame of the view inside the button:
var resetButton: some View {
Button(action: {
print("reset")
}) {
Image(systemName: "arrow.clockwise")
.frame(width: 44, height: 44) // Or any other size you like
}
.background(Color.yellow)
}
This blog post pointed me in the right direction, I needed to add some padding directly to the Image.
Button(action: {
// Triggered code
}) {
Image(systemName: "plus")
.font(.system(size: 22, weight: .regular))
.padding(.vertical)
.padding(.leading, 60)
}
.background(Color.red) // Not needed

Resources