Span two Buttons across HStack in SwiftUI (Big Sur) - macos

I have a problem getting the correct alignment for two buttons (Cancel and OK) in a sheet using SwiftUI.
I want the two buttons to appear at the bottom of the sheet so that the two of them span the whole horizontal width of the sheet (minus padding).
I've found several answers (such as setting the maxWidth to .infinity or by putting the text contend of a button in a separate Text view and surrounding it by Spacers) but none of them seem to work for me. The only one that works somewhat is by creating my own ButtonStyle. But then I have to recreate the whole default ButtonStyle (different for default button, for day and night mode,...)
The code I have now is:
var body: some View {
VStack() {
...
HStack {
Button("Cancel",action: {
isPresented.toggle()
})
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.keyboardShortcut(.cancelAction)
.border(Color.red)
Button("OK", action: {
isPresented.toggle()
let newServer = Server(name: name, url: url, port: port, autoConnect: autoConnect)
do {
try model.add(server: newServer)
} catch {
print("Could not add server.")
}
})
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.keyboardShortcut(.defaultAction)
.border(Color.red)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.border(Color.blue)
}
.padding()
.frame(minWidth: 300, maxWidth: 300)
}
This results in the following sheet:
I would like both buttons to fill the area surrounded by the red border.
I'm kinda at a loss on what to try next!

as the red borders show, the buttons do extend to fill the area.
To get the background gray as in the picture, you could use something like:
.background(Color(UIColor.systemGray4))
as the last modifier of each buttons.

Default button style is rendered around provided label content, so possible solution is to calculate needed width for button label dynamically and apply it explicitly.
Note: please also see my comment for alternate
Demo prepared with Xcode 12.5 / macOS 11.3
struct ContentView: View {
#State private var cancelWidth = CGFloat.zero
#State private var okWidth = CGFloat.zero
var body: some View {
VStack() {
Text("Demo here!")
HStack(spacing: 0) {
Button(action: {
}) { Text("Cancel").frame(width: cancelWidth) }
.frame(minWidth: 0, maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
.onPreferenceChange(ViewWidthKey.self) { self.cancelWidth = $0 }
.padding()
.border(Color.red)
.keyboardShortcut(.cancelAction)
Button(action: {
}) { Text("OK").frame(width: okWidth) }
.frame(minWidth: 0, maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
.onPreferenceChange(ViewWidthKey.self) { self.okWidth = $0 }
.padding()
.keyboardShortcut(.defaultAction)
.border(Color.red)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.border(Color.blue)
}
.padding()
.frame(minWidth: 300, maxWidth: 300)
}
}
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}

Related

Using SwiftUI DragGesture to scale down and remove the whole view but lead to infinity loop

I want to implement the animation like AppStore. Drag down detail page to scale down and remove itself view.
But when I started to drag, the console keeps printing var scale is changing and app is freezes. Looks like go into infinity loop. Here is my code:
import SwiftUI
struct TestView: View {
#State var showDetail = false
var body: some View {
ZStack {
Text("First View")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.accentColor)
.onTapGesture {
withAnimation {
showDetail = true
}
}
if showDetail {
TestDetailView(showDetail: $showDetail)
}
}
.ignoresSafeArea()
}
}
struct TestDetailView: View {
#State var scale: CGFloat = 1 {
didSet {
print("scale: \(scale)")
}
}
#Binding var showDetail: Bool
var body: some View {
ScrollView {
VStack(spacing: 0) {
Text("Upper Part")
.foregroundColor(.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
.gesture(DragGesture(minimumDistance: 0)
.onChanged { val in
let s = val.translation.height / UIScreen.main.bounds.height
if s > 0 && 1 - s > 0.7 {
scale = 1 - s
if scale < 0.8 {
withAnimation {
showDetail = false
}
}
}
}
)
Text("Lower Part")
.foregroundColor(.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.primary)
.onTapGesture {
withAnimation(.easeInOut) {
showDetail = false
}
}
}
.frame(height: UIScreen.main.bounds.height)
}
.scaleEffect(scale)
.animation(.easeInOut, value: showDetail)
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
I know it's relative to the drag gesture and setting scale but I don't know how to fix that.
The images above is what I want to implement, my code simplified
the layout, you may try to drag down the upper part, then you'll know what happen.

view display on the right of the window

hello i have an issue with my project on Xcode when I use navigation view, the window display not normally and appear on the right of the window already displayed,
here is the code.
NavigationLink(destination: Acceuil())
{
HStack{
Image("icone_connexion")
.font(.system(size: 15))
// .scaledToFill()
Text("se connecter")
.font(.system(size: 30))
}.cornerRadius(60)
.frame(width: 400, height: 60)
} .background(Capsule().fill(Color(red: 55/255, green: 66/255, blue: 114/255, opacity:1)))
.frame(width: 400, height: 60) //fin navigationlink
.buttonStyle(PlainButtonStyle())
I would like that the new window replace the older one:)
On macOS it is the standard behavior in NavigationView to show the views (parent view and destination view) next to each other: https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/column-views/
If you don't want that you can do:
NavigationView {
...
}
.navigationViewStyle(.stack)
so I found this code
import SwiftUI
struct ContentView: View {
#State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
it work grate for me so thanks you for your help

SwiftUI: Double click on white space in a list row in macOS?

I'm trying to get the entirety of a row to be double clickable.
Here is my code:
import SwiftUI
let colourArray = ["red","blue","green"]
struct ContentView: View
{
var body: some View
{
List
{
ForEach (colourArray, id: \.self)
{ colour in
ArrayRow(colour: colour)
.gesture(TapGesture(count: 2).onEnded
{
print("double clicked")
})
.padding(.bottom, 15)
}
}
}
}
struct ArrayRow: View
{
let colour: String
var body: some View
{
HStack(alignment: .top)
{
Text(colour)
.frame(width: 150, alignment: .leading)
Text(colour)
.frame(width: 150, alignment: .leading)
Text(colour)
.frame(width: 150, alignment: .leading)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The double click works great, but only if you double click on the text. If you double click on the white space then nothing happens. How do I get the whole row to respond to the double click?
You can extend the tappable/clickable area with .contentShape
ArrayRow(colour: colour)
.contentShape(Rectangle())
.gesture(TapGesture(count: 2).onEnded
{
print("double clicked")
})
.padding(.bottom, 15)

How to set a max width on a subview of HSplitView

I'm having some trouble with HSplitView on macOS, and I'm not sure if it's a bug or something I'm doing wrong.
I'm trying to create an app with a [Sidebar | Main Content | Inspector] layout. This is similar to the SF Symbols app, where you can view more information about a selected icon.
The approach I've taken is to have the Main Content and Inspector views be in an HSplitView. Having three views in a NavigationView only seems to use the Mail pattern of [List | List | Detail], which is not the layout I'm looking for.
The problem I'm seeing is that when I set a maxWidth on the Inspector view and drag the divider to the left, the Main Content view resizes to be smaller than the available space. The parent HSplitView doesn't resize, so I'm not sure if I'm using the wrong modifiers or using them in the wrong places.
I've setup a basic test case on github. If y'all could offer and help or guidance, that would be amazing.
Observed behaviour
Expected behaviour
import SwiftUI
struct A: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text("Pane A")
Spacer()
}
Spacer()
}
.frame(
minWidth: 200,
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .leading
)
.background(Color.red)
.foregroundColor(.black)
.layoutPriority(1)
}
}
struct B: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text("Pane B")
Spacer()
}
Spacer()
}
.frame(
minWidth: 200,
idealWidth: 250,
maxWidth: 300,
maxHeight: .infinity
)
.background(Color.purple)
.foregroundColor(.black)
}
}
struct ContentView: View {
var body: some View {
HSplitView {
A()
B()
}
.frame(
minWidth: 0,
maxWidth: .infinity,
maxHeight: .infinity
)
.background(Color.blue)
}
}e
Thanks!
I've had the same problem for several days, and I solved it in a flash.
Need to drag View
HSplitView {
View1()
View2()
}
Intuitively, we will set the maximum width of View1, but when SwiftUI drags, View1 exceeds the maximum width, it will display an inexplicable error
HSplitView {
View1()
.frame(maxWidth: 300)
View2()
}
Solutions
Set the minimum width of View2, and let View automatically fill all spaces. There will be no drag problem
HSplitView {
View1()
View2()
.frame(minWidth: 500)
}
Embedding one split view inside other may work.
Some problems arise if using GeometryReader.
HSplitView
{
View1()
HSplitView
{
View2()
View3()
}
}

SwiftUI: (Mac) how to fill list row background without white spaces

I want to fill all the list row background with specific color, I made simple this:
struct ContentView: View {
var body: some View {
List(0 ..< 5) { index in
VStack {
Text("Placeholder")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color(.orange))
}
}
}
but the result contains a white spaces around the row:
so, how to fill all row background?
Use this modifier on the list:
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
If you want same color for all rows
struct ContentView: View {
var body: some View {
List(0 ..< 5) { index in
......
}
.colorMultiply(.orange)
}
Hope this helps!
Here's a way that works (in beta 6, since .listRowInsets() seems to be broken), but doesn't feel like a very good answer. You can use negative padding to force the background to draw outside of the list row (you might need to fiddle with the values a bit):
struct ContentView: View {
var body: some View {
List(0 ..< 5) { index in
VStack {
Text("Placeholder")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color(.orange))
.padding(.top, -6)
.padding(.bottom, -6)
.padding(.leading, -18)
.padding(.trailing, -18)
}
}
}

Resources