I am looking to have multiple columns within a SwiftUI form section. I have seen other potential fixes and using .onTapGesture, but that seem's very 'hacky' and doesn't provide the same aesthetics a form normally does on the iPad.
In my code I have one ForEach loop that will have around 15 rows, I want to split these and ensure they all appear on the user's screen at once, therefore eliminating the need to scroll and search.
Section(header: Text("Current Players On the Field")){
ForEach(team.players){player in
if(player.active == true){
Button(action: {
//Button Action Here
}){
PlayerSelectTemplate(player: player)
}
.padding(5)
}
}
}
I have tried implementing LazyV/LazyH Grids in but the buttons all seem to merge and become one, making the separate buttons completely redundant.
As mentioned above, I want to try and avoid the .onTapGesture solve, as it doesn't look as good as having individual buttons. Any ideas?
Related
I select the systemImage "map" and "person" for the tabItem, but the images are in filled format which must be in hollow format. What's the reason?
struct TestView: View {
var body: some View {
TabView {
Text("Map!")
.tabItem {
Label("Map", systemImage: "map")
}
Text("Profile")
.tabItem {
Label("Person", systemImage: "person")
}
}
}
}
Xcode: 13.1
SF Symbols: 3.1
This is standard SwiftUI behaviour in iOS 15, as it implements by default the recommendations from Apple’s Human Interface Guidelines, which says tab bars should use filled variants of SF Symbols, while sidebars on iPad should use the outline variant.
The effect is achieved by iOS automatically applying the .symbolVariants environment value, as noted in the symbol variants documentation:
SwiftUI sets a variant for you in some environments. For example, SwiftUI automatically applies the fill symbol variant for items that appear in the content closure of the swipeActions(edge:allowsFullSwipe:content:) method, or as the tab bar items of a TabView.
If you absolutely want to get rid of the fill mode, it’s deliberately made tricky but not impossible. You have to override the supplied \.symbolVariants environment variable directly on the Label element, inside your tabItem declaration:
Text("Map!")
.tabItem {
Label("Map", systemImage: "map")
.environment(\.symbolVariants, .none)
}
Using the .symbolVariants(.none) modifier, or trying to set the environment value higher up the view graph, won’t work.
Now that you see how to override the effect, I would still advise using the filled forms in the tab bar. Given that the tab bar no longer has a background colour difference from the rest of the page in many cases, the extra visual weight given to tab items by use of the filled variant lends the right amount of visual weight to those elements.
I started looking at Paul Hudson's Hacking with Swift tutorial on positioning views in a grid. If I run the code given in the first listing, I get exactly what's shown in the corresponding screenshot. In other words, this code ...
import SwiftUI
struct ContentView: View {
let data = (1...100).map { "Item \($0)" }
let columns = [
GridItem(.adaptive(minimum: 80))
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: \.self) { item in
Text(item)
}
}
.padding(.horizontal)
}
.frame(maxHeight: 300)
}
}
... ran on an iPad 11 Pro Max simulator gets me the following:
So far so good.
But I want to show a grid of Text views with a bit more content, so I change the first line of my view to ...
let data = (1...100).map { "Item \($0) - more content" }
... but this gets me ...
This is where my understanding falls apart. I would like to see a grid where each Text view is given the amount of horizontal space needed to accommodate the longest content, without line breaks or truncation. Something like this:
Item 1 - more content Item 2 - more content
Item 3 - more content Item 4 - more content
...
Item 42 - more content Item 43 - more content
Item 44 - more content Item 45 - more content
...
Item 142 - more content Item 143 - more content
Item 144 - more content Item 145 - more content
...
Item 7142 - more content Item 7143 - more content
Item 7144 - more content Item 7145 - more content
...
And if my data was long enough that strings were longer than half the width of the screen, I'd expect the grid to utilize just a single column.
Why don't the Text views take up more horizontal space? We've only specified a minimum for our adaptive GridItems, so why would they not expand horizontally as needed to accommodate the longest string? The documentation for GridItem.Size.adaptive says (emphasis mine):
This approach prefers to insert as many items of the minimum size as possible but lets them increase to the maximum size.
So how would I get the grid to let these views increase to their maximum size (which is .infinity by default)?
I've tried adding .fixedSize() to each Text, hoping it would force them to retain their ideal size. But this does not work:
Similarly, I've tried .lineLimit(1) as well, but this results in truncation:
I'm aware of three similar questions (here, here, here) but they are trying to achieve a variable line length with the given data. In my case I really do want a grid. I just want each grid item to take up as much horizontal space as the largest one. How could I achieve this?
I feel like I'm missing something simple about the way SwiftUI layout (or that of a grid) works. I know that during layout the parent view proposes a size to its children, and each child responds with its desired size. In this case it looks like the grid has already decided it will fit four columns on the screen, so it only offers roughly a quarter of its width to each Text. I don't follow why that would be the case when we've asked the grid items to be adaptive and only provided a minimum width.
In a "teach a man to fish ..." fashion, I'd greatly appreciate any advice or tooling that would help me understand how you arrive at your specific answer. For instance, is there a way for a developer to follow (debug print perhaps?) the proposals and counterproposals between parent and child views? Is there any documentation, explanation, etc. to learn more about what exactly goes on during the layout pass? Any detailed explanation of how exactly a grid decides on the sizes of adaptive GridItems?
As the title says, is there any way I can detect (e.g. using a #State variable) when either any context menu is open, or the context menu of a specific view is open?
As a basic idea, I would like to print something if it is open. This does not work:
.contextMenu {
print("open")
}
This also does not seem to work:
.contextMenu {
EmptyView()
.onAppear {
print("open")
}
}
How could i make this work?
Edit: Why i think its even possible to do it or at least possible to make it look like its possible: On instagram, one can see individual posts as a square only. However using long-press, a context menu opens, and now the post shape is different, but even more there is also a small title above it.. How would one do that? Did they modify the view when the context menu open or is the grid view the post is in before just hiding those details (true image shape + image title) but they are rendered already?
Screenshots:
A possible approach is to use simultaneous gesture for this purpose, like
Text("Demo Menu")
.contextMenu(menuItems: {
Button("Button") {}
})
.simultaneousGesture(LongPressGesture(minimumDuration: 0.5).onEnded { _ in
print("Opened")
})
Tested with Xcode 13.2 / iOS 15.2
Is it possible to include a TabView in SwiftUI that doesn’t change views? I need a bottom menu bar that has 4 items in, but these items do not all need to move to a new view. Can I change the expected behaviour of TabView and utilise the buttons it provides or do I need to just make my own?
If you're using the newest beta (2020), you could use a toolbar.
On iOS you can set the placement of the a ToolbarItem as the bottom bar, and make look similar to a bottom tab bar. Back in UIKit you would probably have used a toolbar as well, since TabBars are associated with navigation.
Anyway, here's an example:
HStack {
ForEach(1..<5) { number in
Text("\(number)")
}
}
.toolbar(items: {
ToolbarItem(placement: .bottomBar) {
Button("Test", action: {})
}
})
Add a toolbar modifier to your view, then add the items with whatever content you want. Since the ToolbarItem uses a view builder, you can give it any view.
If you want multiple buttons spread out nicely, replace the Button inside the ToolbarItem with an HStack and have multiple buttons/spacers/whatever you need to get the layout you want.
Note: the .bottomBar placement might not be available on all platforms
I'd like to create a Text view that shows two lines of content, and either cut off the remaining text if there is too much, or just display one or two empty lines if the text isn't long enough or is even an empty string. Is this currently possible? Could I create my own Text view that could somehow behave this way?
For limiting the lines to only takes two, Use .lineLimit(2) modifier.
For showing empty space if it's empty, you may use .frame() modifier. But if you need to find exact hight of the two line label and apply it, use a placeholder inside a ZStack like this:
ZStack(alignment: .top) {
Text("\n ").hidden() // Just a place holder to hold required height to show two lines
Text("Text").lineLimit(2)
}
UPDATE duo to comments
Remember, there are other issues related to other parts of the SwiftUI, For example if you need to use it inside a StackView, you must specify the layoutPriority since it's not required by default.
NavigationView {
VStack {
ZStack(alignment: .top) {
Text("\n ").lineLimit(2)
Text("Text").lineLimit(2)
}.layoutPriority(1000)
NavigationLink(destination: Text("Destination")) {
Text("Next")
}
}
}
And if you see SwiftUI likes to trim extra spaces, you can use hidden characters in the placeholder to make it retain. Use this if needed:
(Text(" ")+Text("\n")+Text(" ")).lineLimit(2)
Note that the first and last Text contains an special hidden character.
And yes, you can append Texts together.