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()
}
}
Related
I have a multiplatform SwiftUI project and I noticed on the macOS side that some of the views make my window too tall and I, the user, can't make it shorter. I noticed this with two particular views and so created a sample project containing a simplified version to duplicate this issue and figure it out. I was able to duplicate the issue and, through commenting out lines and running it, found the source seems to be the TextField itself. Is there anyway to make the window shorter as is? Is there something I can use instead of a TextField to fix this? Or am I stuck with this until the framework is updated? Figured I'd ask here in case anyone has any insight.
For your ease at replicating I'll include the code and then, below, screenshots explaining my issue. Thanks
Main ContentView:
struct ContentView: View {
var body: some View {
TabView {
ExampleViewOne()
.tabItem {
Label("Example One", image: "one")
}
.tag("one")
ExampleViewTwo()
.tabItem {
Label("Example Two", image: "two")
}
.tag("two")
}
.background(Color.green)
}
}
Example One struct for the first tab:
struct ExampleViewOne: View {
// Editable Fields
#State var aString:String = "name"
#State var aBoolean:Bool = false
var body: some View {
VStack(alignment: .center) {
Form {
Text("Hello Everyone!")
.italic()
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
Button(action: {
print("A Button")
}, label: {
HStack(spacing: 10) {
Text("A Button")
}
.frame(maxWidth: .infinity)
})
TextField("Name:", text: $aString, prompt: Text("Nickname"))
// TextField("Username:", text: $aString, prompt: Text("root"))
// TextField("Password:", text: $aString, prompt: Text("********"))
// TextField("I.P. Address:", text: $aString, prompt: Text("XXX.XXX.X.XXX"))
Button(action: {
print("Another button")
}) {
HStack(spacing: 10) {
Image(systemName: "questionmark")
Text("Another button")
Image(systemName: "questionmark")
}
.frame(maxWidth: .infinity)
}
Divider()
Toggle(isOn: $aBoolean, label: {
Text("This is the label of this switch")
})
}
.background(Color.purple)
Spacer()
.background(Color.red)
HStack(spacing:10) {
Button(action: {
print("Delete")
}) {
HStack(spacing: 10) {
Image(systemName: "trash.fill")
Text("Delete (⌘ d)")
}
.frame(maxWidth: .infinity)
}
.keyboardShortcut("d", modifiers: .command)
Button(action: {
print("Save")
}) {
HStack(spacing: 10) {
Image(systemName: "checkmark")
Text("Save")
}
.frame(maxWidth: .infinity)
}
.keyboardShortcut("s", modifiers: .command)
}
.background(Color.blue)
}
.padding()
.background(Color.black)
}
}
Example Two struct for the second tab
struct ExampleViewTwo: View {
// Editable Fields
#State var aString:String = "name"
#State var aBoolean:Bool = false
let theExamples = ["example one", "example two", "example another"]
var body: some View {
VStack(alignment: .center) {
Form {
TextField("Display Name:", text: $aString, prompt: Text("Name"))
// TextField("Directory (Folder) Name:", text: $aString, prompt: Text("Directory Name"))
ScrollView {
Text("These are the examples.")
ForEach(theExamples, id:\.self) { number in
Text(number)
}
}
}
.background(Color.purple)
Spacer()
.background(Color.red)
HStack(spacing:10) {
Button(action: {
print("Delete")
}) {
HStack(spacing: 10) {
Image(systemName: "trash.fill")
Text("Delete (⌘ d)")
}
.frame(maxWidth: .infinity)
}
.keyboardShortcut("d", modifiers: .command)
Button(action: {
print("Save")
}) {
HStack(spacing: 10) {
Image(systemName: "checkmark")
Text("Save")
}
.frame(maxWidth: .infinity)
}
.keyboardShortcut("s", modifiers: .command)
}
.background(Color.blue)
}
.padding()
.background(Color.black)
}
}
Explanation:
I set different background colors to each view, to tell them apart, and then played with the spacers before commenting out the code in the top (purple) view and slowly bringing it back until the height was restricted again.
The spacer background color isn't shown on either tab but it is used on Example One. For Example One the views are centered vertically if the spacer isn't there yet the height isn't affected. On Example Two the height also stays the same regardless of the spacer's presence but the space itself is taken up by the purple view instead.
The first tab with the spacer included is laid out properly but I want the black space between the two smaller:
Without the spacer the views are centered but the height is still unable to be shortened by the user:
The second tab looks the same regardless whether there's a spacer or not with the purple view taking up the space that is somehow required:
After commenting out the code and bringing it back again I realized the culprit was the TextField as without them the window could be as small as I wanted it. With one TextField uncommented the height was restricted. With each additional TextField uncommented the height became taller and taller.
With no TextField in the view I could make the Window however small I wanted:
As soon as I added a TextField back the height was restricted:
With each additional TextField added the height needed became larger and thus the empty black space was larger. I can't make the Window smaller than this:
The same could be said for the second example window. No TextFields means I could make the Window really short. The uncommenting of a TextField made the height increase without me making it smaller. The additional TextFields makes it taller and the purple space larger:
Is there anyway to allow me to make this Window smaller in the presence of the TextFields?
My other views don't seem to have this problem but they're contained within a ScrollView. I could embed these too but now I'm really curious what's causing it.
Thanks for any help.
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
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()
}
}
I'm working on an app for iPad that is to be used to manage a personal cash budget. I'm working with XCode 12 building for iOS 14 using SwiftUI. I have most of the underlying MODEL work done, but I'm struggling with the UI. I've programmed in various languages since 1979. (Yes I' old and doing this is a hobby :-). I can not for the life of me figure out the technique to pop an edit/entry view over a parent view. As an example, I have an app the I also use that does just this. I'm attaching an image that shows what I'd like to be able to do. I've tried .overlay() and ZStack, but what I've tried just doesn't give me what I want. If you could look at the image I'm posting and point me in the right direction for even just for technique I'd be really appreciative....
Image of entry view popped over subview:
Image of subview before:
The image looks like the new view is being presented via the .sheet() modifier. This is the most common approach to present a view like this. I would just note that it looks slightly different on iPad (like the image above) vs iPhone (extends full width of screen).
struct Test: View {
#State var showSheet: Bool = false
var body: some View {
Text("Hello, World!")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("Next view")
})
}
}
Alternatively, here are 2 other methods to present a sheet, which can be a little more customizable (custom animations/transitions) if required.
struct Test2: View {
#State var showSheet: Bool = false
var body: some View {
ZStack {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
showSheet.toggle()
}
if showSheet {
Text("Next view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.animation(.spring())
.transition(.move(edge: .bottom))
}
}
}
}
struct Test3: View {
#State var showSheet: Bool = false
var body: some View {
ZStack {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
showSheet.toggle()
}
Text("Next view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.opacity(showSheet ? 1.0 : 0)
.animation(.spring())
}
}
}
I have created a round button with following code:
struct RoundButton : View {
var value = "..."
var body: some View {
GeometryReader { geometry in
Button(action: {}) {
VStack {
Text(self.value)
.font(.headline)
.color(Color("NightlyDark"))
Text("MIN")
.font(.footnote)
.color(.white)
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
.clipShape(Circle())
}
}
}
But when clicking the button, the shape gets deformed. Any idea why this happens ?
Same happens when i use .mask(Circle())
Is this a beta thing or normal behavior ? And does anyone maybe know a better way to create rounded buttons ?
what happens here is Button Considers all screen[width + height] as their frame by default.
so you have to set Button also.
I think it's the default behavior in watchOS
Here Your Code :
struct RoundButton: View {
var value = "..."
var body: some View {
GeometryReader { geometry in
Button(action: {}) {
VStack {
Text(self.value)
.font(.headline)
.foregroundColor(Color("NightlyDark"))
Text("MIN")
.font(.footnote)
.foregroundColor(.white)
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.clipShape(Circle())
}
}
}
Note: i'm using Xcode 11 Beta 4