Applying padding to SwiftUI Picker with menu style is causing multiline text - swift

I have a Picker with menu style presenting a label with an icon and text. When applying a padding to any views containing the picker, the picker will behave as though there is not enough space and run to multiple lines even though there is enough space.
Sample Code
struct ContentView: View {
let options = [
"Some Long Text"
]
private var selectedOption: Binding<String> =
.constant("Some Long Text")
var body: some View {
HStack {
Text("Some Text")
Picker("Work Type", selection: selectedOption) {
ForEach(self.options, id: \.self) {
Label($0, systemImage: "pencil.tip.crop.circle")
}
}.pickerStyle(.menu)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
VStack {
ContentView()
ContentView()
.padding()
}
}
}

This might be a layout bug in SwiftUI. There is clearly enough space to put it all in one line, even with the padding. You may want to file a bug report to Apple.
However, you can easily fix this by applying the fixedSize() modifier to your Picker:
Picker("Work Type", selection: selectedOption) {
// ...
}
.pickerStyle(.menu)
.fixedSize(horizontal: true, vertical: false)
This will make sure that the Picker doesn't concern itself with external sizing requirements, ignores the proposed with and just uses renders with its ideal size.
Please note that this might lead to unintended behavior. For example, when the user chooses a bigger font size in accessibility settings, the text in the Picker will never break and might be rendered off-screen. So make sure to think through all your use cases.
PS: You can also use a Menu instead of a Picker with .menu style. Of course, you won't get the selection behavior for free and have to do it manually, but it doesn't have this layout bug.

This seems like intended behavior to me. Because of the padding applied around the HStack (specifically at the horizontal edges) there is not enough space to render the form label on one line. If you were to change the size of the screen (for example using an iPad simulator or using landscape mode) the label would have enough room and be displayed on one line. I tested this code on an iPad simulator and it worked as expected.
If you want to force the label text to be rendered on one line, use fixedSize(horizontal:vertical:). Though this can lead to unexpected behavior.
Picker("Work Type", selection: selectedOption) {
ForEach(self.options, id: \.self) {
Label($0, systemImage: "pencil.tip.crop.circle")
}
}
.pickerStyle(.menu)
.fixedSize(horizontal: true, vertical: false)

Related

SwiftUI ScrollView does not respond to keyboard

Thanks for taking your time to help others :)
Problem description:
I want to bring up the ScrollView content as keyboard shows up. And I can't.
Despite, if ScrollView is turned upside down... it works!!! But I can't do that because I have to implement .contextMenu(...) and this produces an even worse bug (detailed in this post).
App must support iOS 14.
Simple code demo to show what happens.
import SwiftUI
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
ScrollView() {
LazyVStack {
ForEach(1..<201, id: \.self) { num in
Text("Message \(num)")
}
// .upsideDown() // With these modifiers, will prompt up the scrollView content
}
}
// .upsideDown() // With these modifiers, will prompt up the scrollView content
TextField("Your text here", text: $text)
.textFieldStyle(.roundedBorder)
.padding()
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Example app")
}
}
extension View {
func upsideDown() -> some View {
self.rotationEffect(.degrees(180))
}
}
GIF resources to see behaviour:
Good result (when upside down):
Bad result (on normal scrollView):
What we have checked?
Already tried this solution, but does not help.
Tried setting an offset to ScrollView of keyboards height when it does come up but... nothing happens.
Questions
Why does this happen when it is upside down? (and not at normal scrollview)
Why on both cases it brings up the TextField but does NOT bring up the content of Scrollview if is not upside down???

SwiftUI List on macOS: highlighting content in the selected row?

In a SwiftUI List, when a row is selected, a blue selection is drawn in the selection, and foreground Text with the default primary color is automatically made white. For other views with custom colors, I'd like to be able to make sure they become tinted white to match the system apps. For example, see the blue dot on the selected row:
Example code:
List(selection: $selection) {
ForEach(0..<4) { index in
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.blue)
Text("Test")
}
.tag(index)
}
}
Any tips on how to achieve the correct result here? 🙏
With NSTableCellView, there is the backgroundStyle property, but I couldn't find anything like this.
I've searched all of the available environment variables, and couldn't find anything appropriate.
I have also tried manually including an isSelected binding on a view for each row, but the selection binding is not updated by List until mouse-up, while the highlight is updated on mouse-down and drag, so this results in a flickery appearance and is not right either.
I'm also looking to customize not just a single SF Symbol image, but also a RoundedRectangle that is drawn with a custom foreground color that I want to become white upon selection.
Approach
Use a Label for the cell
Code
struct ContentView: View {
#State private var selection: Int?
var body: some View {
List(selection: $selection) {
ForEach(0..<4) { index in
Label("Test", systemImage: "circle.fill")
.tag(index)
}
}
}
}
Screenshot

SwiftUI Positioning View [duplicate]

I've seen similar questions like this one here on Stack Overflow, but none of them have been able to answer my question.
I have created a List, but I want to remove the padding space (marked with a red arrow) above the content in the List. How can I remove it when using a GroupedListStyle?
This is a List within a VStack within a NavigationView that is displayed via fullscreenCover:
var body: some View {
NavigationView {
VStack {
taskEventPicker
myList
}
.navigationBarTitle("Add Task", displayMode: .inline)
}
}
where taskEventPicker is a segmented Picker (boxed in green):
var taskEventPicker: some View {
Picker("Mode", selection: $selection) { /* ... */ }
.pickerStyle(SegmentedPickerStyle())
.padding()
}
and myList is the form in question (boxed in yellow):
var myList: some View {
List { /* ... */ }
.listStyle(GroupedListStyle())
}
What I've Tried
Removing the header of the list
Hiding the Navigation Bar title
Setting UITableView.appearance().tableHeaderView to an empty frame
Attempting UITableView.appearance().contentInset.top = -35
Setting listRowInsets (this affects all list rows)
Note: I'm looking for an answer that can apply to the GroupedListStyle. I understand that this issue does not occur with PlainListStyle. This padding issue also occurs with the default listStyle.
Thanks for the help!
Xcode version: 12.5
Firstly, I would say that GroupedListStyle is working as intended.
On iOS, the grouped list style displays a larger header and footer
than the plain style, which visually distances the members of
different sections.
You say you have tried this, but it does work for me (Xcode 12.5.1):
List { ... }
.onAppear(perform: {
UITableView.appearance().contentInset.top = -35
})
You could also hide the list header, by using a ZStack with the List at the bottom of the stack and the Picker over the top. The Picker does have transparency, so you would also have to add an opaque view to act as background for the Picker.
var body: some View {
NavigationView {
ZStack(alignment: .top) {
List { ... }
.listStyle(.grouped)
.padding(.top, 30)
Color.white
.frame(height: 65)
Picker { ... }
.pickerStyle(.segmented)
.padding()
}
.navigationBarTitle("Add Task", displayMode: .inline)
}
}
As far as I can see this just appears the same as PlainListStyle would do, but I assume you have a particular reason for wanting to use GroupedListStyle.
The contentInsent part of the accepted answer works perfectly fine unless for instance as in my case, at the end of a navigation stack you want to use it in some detail view containing a List with navigationBarTitleDisplayMode set to .inline. When navigating back in the stack to a view using another List with navigationBarTitleDisplayMode set to .large, the List content and NavigationBar interlace awfully. And they probably might as well do whatever the navigationBarTitleDisplayMode is set to.
Switching to listStyle(.plain) in my case isn't a feasable option, because then in the detail view, I loose the beautifully animated built-in transitions to and from List's EditMode, that seem to exist only in the grouped listStyle varieties.
Finally, after quite a few days of frustration I figured out that tableHeaderView is the approach that works best for my problem - and of course, it works just as well to solve the original question. It's all in here ...:
XCode documentation
... and in this sentence:
"When assigning a view to this property [i.e. tableHeaderView], set the height of that view to a nonzero value"
So I do like:
List {
// bla bla bla
}
.listStyle(.grouped)
.onAppear {
let tableHeaderView = UIView(frame: .zero)
tableHeaderView.frame.size.height = 1
UITableView.appearance().tableHeaderView = tableHeaderView
}
It's as simple as that. Hope it might help you, too!
Give a header to the first section in your list: Section(header: Spacer(minLength: 0))
Disclaimer: this doesn't totally remove the top spacing, but it does yield the same result as the native Settings app.
Note: This worked for me on iOS 14.5
VStack {
List {
Section(header: Spacer(minLength: 0)) {
Text(verbatim: "First Section")
}
Section {
Text(verbatim: "Second Section")
}
}
.listStyle(GroupedListStyle())
}
Another option is to add
List { ... }.offset(x: 0, y: -30).edgesIgnoringSafeArea(.bottom)
to the List. Since you won't be able to scroll all the way up when using this add a Spacer(minLength: 30) at the very bottom of the List.
I had a similar setup where I had some views and a List in a VStack and I had an undesired space that appeared on the top of my List. In my case I needed to set the VStack spacing to 0 and that solved the issue because it was actually just spacing from the VStack.
VStack(spacing: 0) { ... }
my 2 cents. I arrived here for the same reason.
take a look at:
https://developer.apple.com/forums/thread/662544
it' seems the correct approach.
extension View {
/// Clear tableview and cell color
func tableClearColor() {
let tableHeaderView = UIView(frame: .zero)
tableHeaderView.frame.size.height = 0.5
UITableView.appearance().tableHeaderView = tableHeaderView
}
}

Why does SwiftUI View background extend into safe area?

Here's a view that navigates to a 2nd view:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
SecondView()
} label: {
Text("Go to 2nd view")
}
}
}
}
struct SecondView: View {
var body: some View {
ZStack {
Color.red
VStack {
Text("This is a test")
.background(.green)
//Spacer() // <--If you add this, it pushes the Text to the top, but its background no longer respects the safe area--why is this?
}
}
}
}
On the 2nd screen, the green background of the Text view only extends to its border, which makes sense because the Text is a pull-in view:
Now, if you add Spacer() after the Text(), the Text view pushes to the top of the VStack, which makes sense, but why does its green background suddenly push into the safe area?
In the Apple documentation here, it says:
By default, SwiftUI sizes and positions views to avoid system defined
safe areas to ensure that system content or the edges of the device
won’t obstruct your views. If your design calls for the background to
extend to the screen edges, use the ignoresSafeArea(_:edges:) modifier
to override the default.
However, in this case, I'm not using ignoresSafeArea, so why is it acting as if I did, rather than perform the default like Apple says, which is to avoid the safe areas?
You think that you use old background modifier version.
But actually, the above code uses a new one, introduced in iOS 15 version of background modifier which by default ignores all safe area edges:
func background<S>(_ style: S, ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View where S : ShapeStyle
To fix the issue just replace .background(.green) with .background(Color.green) if your app deployment target < iOS 15.0, otherwise use a new modifier: .background { Color.green }.

SwiftUI Label text and image vertically misaligned

I'm using SwiftUI's brand new Label View, running Xcode 12 beta on Big Sur.
As image I use SF Symbol and found an image named "play". But I've noticed the same problem with custom images without any bordering pixels (i.e. spacing is not caused by the image), e.g. PDF icons, so it is probably not related to the image.
In demos by Apple the Text and the image should just automatically align properly, but I do not see that.
struct ContentView: View {
var body: some View {
Label("Play", systemImage: "play")
}
}
Results in this:
Any ideas why the image (icon) and the text is vertically misaligned?
If we give the Button a background color we see more precisely the misalignment:
Label("Play", systemImage: "play")
.background(Color.red)
Results in this:
Probably a bug, so worth submitting feedback to Apple. Meanwhile here is working solution based on custom label style.
Tested with Xcode 12b
struct CenteredLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.icon
configuration.title
}
}
}
struct TestLabelMisalignment: View {
var body: some View {
Label("Play", systemImage: "play")
.labelStyle(CenteredLabelStyle())
}
}
#Sajjon You can add a custom View as a workaround and use Image with Text inside a HStack