NavigationBarTitle doesn't shrink text with minimumScaleFactor() or allowsTightening() - ios

When using .navigationBarTitle on a NavigationView, adding the modifiers .minimumScaleFactor(0.5) or .allowsTightening(true) has no effect. The text is still cut off and remains same size.
My app displays a reference code and a few other bits of information in the navigation bar title and it fits perfectly on Plus sized iPhones but gets trimmed (...) on the smaller screen ones.
This information is quite vital to the operation of the app and we are already short of screen space so really don't have anywhere else to put this info.
With UIKit, I would have created a special TitleView to replace the title text in the NavigationBar, but that doesn't work in SwiftUI.
Is there any workaround for this?
Example:
struct ContentView: View {
let items = ["Chocolate", "Vanilla", "Strawberry", "Mint Chip",
"Pistachio"]
let title = "A long title that doesn't fit on a standard iPhone"
var body: some View {
NavigationView {
List(items, id: \.self) {
Text($0)
}
.navigationBarTitle(title, displayMode: .inline).minimumScaleFactor(0.5).allowsTightening(true)
}
}
}
ScreenShot from iPhone 8 Plus:
Screenshot from iPhone 8:

Related

SwiftUI Picker iOS 16 not filling available space

I am using the following code (example) to render a SwiftUI Picker on iOS:
let strings: [String] = ["short", "very, ver long string"]
#State var selectedString: String = ""
Form {
Picker("Method", selection: $selectedString) {
ForEach(strings, id: \.self) { string in
Text(string)
}
}
}
In iOS 16 the design of the menu style picker has changed (it now includes 2 small chevrons), which is all good, except it no longer fills the available width (as it did on iOS 15). This results in longer strings flowing onto multiple lines even when this isn't neccessary.
Short String (all fine):
Long String (not so good):
I have tried .fixedSize(), which works to some extend but if the string does in fact need to be on two lines this forces the label to be squished. If I add a background to the Picker, it is clear that it only fills around 1/3 of the available space.
Does anyone have any suggestions?
Separate the label from the Picker and wrap it in a HStack.
Form {
HStack {
// the new label text
Text("Method")
.fixedSize() // give other views in HStack space to grow
// push the external label and Picker to the leading and trailing view edges
Spacer()
Picker("Method", selection: $selectedString) {
ForEach(strings, id: \.self) { string in
Text(string)
}
}
.labelsHidden() // the label is in the Text view
}
}
Hide the Picker label by using the .labelsHidden() modifier.
Use the .fixedSize() modifier on the new Text. This will allow the Picker to expand to fit all its contents.
Use Spacer between Text label and Picker to push both items to the edge.

SwiftUI NavigationSplitView on iPad: remove accent from selected item in Sidebar

I am adopting NavigationSplitView for an app to be used on an iPad as well as on an iPhone.
In my code below, I want to highlight the the selected item in the Sidebar only with the word "Selected".
When running for iPad Air 5th generation, the Sidebar appears differently between the preview and the simulator:
In the preview, the behaviour is exactly what I want: the word "Selected" indicates the item visible in the detail view (see picture below without blue border).
However, when running on the simulator, an accent rectangle is visible around the selection (see picture below with blue border). My goal is to remove this rectangle.
In fact, if we remove the .listRowBackground() modifier, in the preview the accent is light gray, in the simulator is blue.
Some things I tried:
Replacing .listRowBackground(EmptyView()) with .listRowBackground(Collar.clear): no success.
Using .accentColor(.clear): it does not work (the rectangle gets white), and moreover it is being deprecated, so I want to avoid it.
Adding .tint(.clear) and listItemTint(.clear): no success.
Replacing List with ForEach, but then I have to re-create the layout of the rows, and the result is not the same.
I need to keep the "yellow" background.
Does anyone know how to remove that rectangle?
My code:
struct MyView: View {
#State private var selected: MyContent?
let list = [MyContent("First"), MyContent("Second"), MyContent("Third")]
var body: some View {
NavigationSplitView {
List(list, selection: $selected) { item in
NavigationLink(value: item) {
HStack {
Text(item.title)
Spacer()
Text(selected == item ? "Selected" : "")
}
.foregroundColor(.primary)
}
.listRowBackground(EmptyView())
}
.scrollContentBackground(.hidden)
.listStyle(.plain)
.background(.yellow)
} detail: {
if let selected {
Text("Detail of \(selected.title)")
}
}
}
}
struct MyContent: Identifiable, Hashable {
let id = UUID()
let title: String
init(_ title: String) {
self.title = title
}
}
What I want to achieve (OK in the preview):
The rectangle I want to remove (running on the simulator):
Deploying for iOS 16.0 on Xcode 14.

SwiftUI InputAccessoryView

I'm trying to build a chat view in SwiftUI and I want to append my input views to the keyboard, so that when I dismiss the keyboard by dragging my view gets moved with it.
When I was using UIKit I overwrote the inputAccessoryView of the ViewController. Is something similar possible with SwiftUI?
EDIT:
I already saw that I can add a UIKit TextField and add a InputAccessory for this text field. However that's not what I want to do. I want to have a global inputAccessoryView in my SwiftUI View and add my custom input view as a subview, so that it is always Visible and not an addition to my TextField.
I see two possible solutions to the behavior you want.
In some cases, SwiftUI views move out of the way of the keyboard automatically
in iOS 15 and later you can create an InputAccessoryView in Swiftui
1: In swiftUI, there are several safe areas which views lay themselves inside of by default. One of these is the keyboard safe area. This areas takes up the full screen of the device when the keyboard is hidden but shrinks to the non keyboard area of the screen when the keyboard is displayed. So in the example code below, the text field should move above the keyboard when it appears and drop down when the keyboard disappears (this does not work on an iPad when the keyboard is in the smaller floating mode).
VStack {
ScrollView {
ForEach(0 ..< 50) { item in
Text("Demo Text")
.frame(maxWidth: .infinity)
}
}
TextField("Enter Text", text: $messageText)
}
2: In iOS 15+, you can create a toolbar in the keyboard location. This essentially acts as an InputAccessoryView does in UIKit. The difference between this and method 1 is that a view in here will only appear when the keyboard is displayed. The one expiation to this is when a wired or wireless keyboard is attached to the iPhone or iPad, the toolbar view will still be displayed just at the bottom of the screen.
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Text("Apears at top of keyboard")
}
}
So putting 1 and 2 together, here is an example that implements both. You can run it in Xcode to help understand how both methods behave
VStack {
ScrollView {
ForEach(0 ..< 50) { item in
Text("Demo Text")
.frame(maxWidth: .infinity)
}
}
TextField("Enter Text", text: $messageText)
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Text("Apears at top of keyboard")
}
}

How can I make a SwiftUI List row background color extend the full width, including outside of the safe area

In a SwiftUI List, how can I make a list row background (set via .listRowBackground()) extend the full width of the view, even under the safe area? E.g. when running in landscape on a wide iPhone (iPhone 12 Pro Max, for example). Currently, the cell appears white on the far left of the cell until the start of the safe area.
Example code:
struct ContentView: View {
var body: some View {
List {
Text("Test")
.listRowBackground(Color(.systemGray3))
}.listStyle(GroupedListStyle())
}
}
This produces a UI as shown below. I would like the grey background of the cell to extend the full width of the device.
I've tried adding .edgesIgnoringSafeArea(.all) to the Text but it makes no difference.
The answer is to put .edgesIgnoringSafeArea([.leading, .trailing]) on the background Color itself, rather than the list item.
struct ContentView: View {
var body: some View {
List {
Text("Test").listRowBackground(
Color(.systemGray3).edgesIgnoringSafeArea([.leading, .trailing])
)
}.listStyle(GroupedListStyle())
}
}

Unable to present ActionSheet via a NavigationBarItem in SwiftUI on an iPad

First, I have looked at a similar question, but it does not address my use case.
Present ActionSheet in SwiftUI on iPad
My issue is that I have a NavigationBarItem in my NavigationView that will toggle an ActionSheet when pressed. This behavior works properly when used on an iPhone.
However, when I use this on an iPad, both buttons on my screen will gray out and nothing happens. Clicking the buttons again will make them active (blue), but again, no sheet is presented.
Finally, if I select the button in the middle of the screen (Show Button), then an ActionSheet is properly presented on an iPad.
I have tested with Xcode 11 & iOS 13.5 and Xcode 12 & iOS 14. There is no change in behavior.
import SwiftUI
struct ContentView: View {
#State private var isButtonSheetPresented = false
#State private var isNavButtonSheetPresented = false
var body: some View {
NavigationView {
Button(action: {
// Works on iPad & iPhone
self.isButtonSheetPresented.toggle()
}) {
Text("Show Button")
}
.actionSheet(isPresented: $isButtonSheetPresented,
content: {
ActionSheet(title: Text("ActionSheet"))
})
.navigationBarTitle(Text("Title"),
displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
// Works on iPhone, fails on iPad
self.isNavButtonSheetPresented.toggle()
}) {
Text("Show Nav")
}
.actionSheet(isPresented: $isNavButtonSheetPresented,
content: {
ActionSheet(title: Text("ActionSheet"))
})
)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
Finally, this is how it appears on an iPad when clicking on "Show Nav":
This is a simplified setup for the screen where this issue occurs. I will need to retain the navigation settings shown, but have included them for clarity.
*** UPDATED ***
While it is not possible for the real app behind this, I did remove the .navigationViewStyle(StackNavigationViewStyle()) setting, which did make an ActionSheet appear, although in the wrong spot as seen below.
This also results in bizarre placement for the Button one accessed via "Show Button".
Yes, it is a bug, but probably different - that Apple does not allow to change anchor and direction of shown ActionSheet, because it is shown, but always to the right of originated control on iPad. To prove this it is enough to change location of button in Navigation
Here is example of placing at .leading position. Tested with Xcode 12 / iOS 14
.navigationBarItems(leading:
Button(action: {
// Works on iPhone, fails on iPad
self.isNavButtonSheetPresented.toggle()
}) {
Text("Show Nav")
}
.actionSheet(isPresented: $isNavButtonSheetPresented,
content: {
ActionSheet(title: Text("ActionSheet"))
})
)
Note: SwiftUI 2.0 .toolbar behaves in the same way, ie. has same bug.
This is an old question but if someone is interested in a turnaround that works on iOS 14:
I have two navigation bar trailing buttons inside .toolbar() and they should open action sheets. I placed an invisible "bar" at the top of the view to use it as an anchor:
var body: some View {
VStack {
HStack {
Spacer()
Color.clear.frame(width: 1, height: 1, alignment: .center)
.actionSheet(/*ActionSheet for first button*/)
Spacer().frame(width: 40)
Color.clear.frame(width: 1, height: 1, alignment: .center)
.actionSheet(/*ActionSheet for second button*/)
Spacer().frame(width: 40)
}.frame(height: 1)
}
}
Cons:
There's a tiny bar/extra space at the top, noticeable especially during scrolling (Maybe putting the Stack in the background with a Stack could remove it?).
You might need to adjust the Spacers' width to try and align the ActionSheets to their respective button.
You can't force the action sheet arrows to always point upwards, I tested this on another simulator and the rightmost ActionSheet had its arrow pointing to the right (the 'illusion' that it came from the button was still there)
Here's how it looks

Resources