PickerView: how to center horizontally and no space? - swiftui

i have this code.
unfortunately the pickerview is not centered horizontally and there is too much space between the buttons and the pickerview (vertically), i now i can use offset, but is there a better way?
var body: some View {
NavigationView {
Form {
Section(header: Text("Hi"), content: {
Button("Alphabet") {
}.frame(alignment: .center)
Button("Ok") {
}.frame(alignment: .center)
HStack {
Picker(selection: $sortedBy,
label: Text(""),
content: {
ForEach(p, id: \.self) { category in
Text(category)
}
}).pickerStyle(WheelPickerStyle())
}
})
}
}
}
#Reiner Fischer...here is the result of your proposol (unfortunately not centered)

The problem is your empty label
label: Text(""),
even if the label is empty it takes some space on the left side of the picker. You can check by just adding some text to the label.
To get rid of the label, adjust your code like this:
.pickerStyle(WheelPickerStyle())
.labelsHidden()
That will center your picker selections
Update 21.02.2020
Hi Chris, enclosed is the code, i tested and that centers the picker:
struct PickerView: View {
let p:[Vital] = [
.init(name: "0"),
.init(name: "1"),
.init(name: "2"),
.init(name: "3")
]
#State private var sortedby = 0
var body: some View {
Picker(selection: $sortedby, label: Text("")) {
ForEach(p) { post in
Text(post.name)
}
}.pickerStyle(DefaultPickerStyle())
.labelsHidden()
}
}

Related

Picker in Menu with Section and Label doesn't work

The following code shows different Pickers.
I want to achieve: MenuPickerStyle with Sections and custom Label like in second example https://imgur.com/a/Q0uxEmh but it is not selectable because it is grayed out
When doing it with the Menu container as recommended in the internet the rows are grayed out. How can I get the wanted behavior?
In the code you can see 4 different examples. The second is the grayed out picker rows.
Tested in Xcode 13.2.1, iOS 15
struct ContentView: View{
#State var number_1 = 0
#State var number_2 = 0
#State var number_3 = 0
#State var number_4 = 0
let numbers = [0,1,2,3,4]
var body: some View{
VStack{
//Picker works, no Section --------------------------
Menu(content: {
Picker(selection: $number_1, content: {
ForEach(numbers,id: \.self){i in
Text("Number: \(i)").tag(i)
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_1)")
})
//Picker is grayed out ---------------------------
Menu(content: {
Picker(selection: $number_2, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_2)")
})
//Picker works, collapsed View not desired behavior --------
Menu(content: {
Picker(selection: $number_3, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
.pickerStyle(MenuPickerStyle())
}, label: {
Text("selected Number = \(number_3)")
})
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
}
}
}
You can put multiple Pickers in a Menu, each with a subset of the available values, and they’ll all work as expected:
Menu {
// First section, with values 0–2
Picker(selection: $num) {
ForEach(0..<3, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
// Second section, with values 3–5
Picker(selection: $num) {
ForEach(3..<6, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
} label: {
Text("selected Number = \(num)")
}
SwiftUI will even stick a separator between the Pickers automatically.
The simplest fix is for case 4 - just rebuild menu (force-refresh) on selection changed:
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
.id(number_4) // << here !!
Tested with Xcode 13.4 / iOS 15.5
I think I've got a workaround.
I tried to use a menu and use Buttons for manipulating the selection. The problem is the mark of the selection if there is already a Image in the label of the button I think.
struct ContentView: View{
#State var number = 0
let numbers = [1,2,3,4]
var body: some View{
Menu(content: {
ForEach(numbers,id:\.self){i in
Section{
Button(action: {
number = i
}, label: {
Text("Number: \(i)")
})
}
}
}, label: {
Text("The selection is: \(number)")
.frame(width: UIScreen.main.bounds.width, height: 20)
.border(Color.green)
})
}
}
I'm not 100% certain I understand, but if your goal is to build a picker that lets the user click on the title and then select an option from a menu, returning the selection to the screen I would suggest doing it as follows:
struct SwiftUIView: View {
#State var option:Int = 0
let numbers = [1,2,3,4]
var body: some View {
VStack {
//Picker to select the option
Picker("Option Picker", selection: $option) {
ForEach(numbers, id: \.self) {
number in
Text("Selection Number \(number)").tag(number)
}
}.pickerStyle(MenuPickerStyle())
//Text confirming the selection won't show before a pick is made
Text(option != 0 ? "You selected option number \(option)" : "")
}
}
}
Let me know if that works for you / is what you were looking for.

Align Subtitles and Footers with LazyVGrid?

I have a question about using LazyVGrid to align the subtitles and footers for each column. Say I have a view like this:
Title
Subtitle A Subtitle B Subtitle C
row1A row1B row1C
row2A row2B row2C
row3A row3B row3C
row4A row4B row4C
row5A row5B row5C
Total A Total B Total C
Currently I use Geometry Reader to display the headers and footers and LazyVGrid to display the data. I have noticed that if I line up the subtitles with a 12 ProMax simulator then view my setup on the 12 Mini simulator that the Mini titles have moved and don't appear very professional. Is it possible to use something like the following with LazyVGrid to not only display the data but also the subtitles and footers? Using pinnedViews: [] I can display the first column header, but how do I display all three subtitles and footers? There is some Apple documentation here on using section headers but nothing on multiple section headers and footers.
private var columns: [GridItem] = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
GeometryReader { g in
ScrollView {
VStack (alignment: .leading) {
ShowTitle()
ShowSubTitle()
LazyVGrid(
columns: columns,
alignment: .leading,
spacing: 10, // vertical spacing
pinnedViews: [.sectionHeaders, .sectionFooters]
) {
Section(header: Text("Subtitle A")
.font(.headline)) {
ForEach(0..<self.categories.catItem.count, id: \.self) { index in
ShowRow(index: index)
}
}
}
I mocked this up off of the Apple example at Grouping Data with Lazy Stack Views. The trick is to put the LazyVStack inside of the ForEach that goes through each section like this:
struct LazyVGridSectioned: View {
let sections = [
ColorData(color: .red, name: "Reds", footerInfo: "Red Footer Info"),
ColorData(color: .green, name: "Greens", footerInfo: "Green Footer Info"),
ColorData(color: .blue, name: "Blues", footerInfo: "Blue Footer Info")
]
var body: some View {
ScrollView {
HStack {
ForEach(sections) { section in
LazyVStack(spacing: 1, pinnedViews: [.sectionHeaders, .sectionFooters]) {
Section(header: SectionHeaderView(colorData: section), footer: SectionFooterView(colorData: section)) {
ForEach(section.variations) { variation in
section.color
.brightness(variation.brightness)
.frame(height: 20)
}
}
}
}
}
}
}
}
struct SectionHeaderView: View {
var colorData: ColorData
var body: some View {
HStack {
Spacer()
Text("Header for \(colorData.name)")
.font(.headline)
.foregroundColor(colorData.color)
Spacer()
}
.padding()
.background(Color.primary
.colorInvert()
.opacity(0.75))
}
}
struct SectionFooterView: View {
var colorData: ColorData
var body: some View {
HStack {
Spacer()
Text(colorData.footerInfo)
.font(.headline)
.foregroundColor(colorData.color)
Spacer()
}
.padding()
.background(Color.primary
.colorInvert()
.opacity(0.75))
}
}
struct ColorData: Identifiable {
let id = UUID()
let name: String
let footerInfo: String // Add parameter in ColorData
let color: Color
let variations: [ShadeData]
struct ShadeData: Identifiable {
let id = UUID()
var brightness: Double
}
// Add it to the init as well
init(color: Color, name: String, footerInfo: String) {
self.name = name
self.color = color
self.footerInfo = footerInfo // Assign it here
self.variations = stride(from: 0.0, to: 0.5, by: 0.1)
.map { ShadeData(brightness: $0) }
}
}

Weird fullScreenCover and sheet behaviour in iOS 15

I have some problem with presenting sheet, sheet that use Identifiable item binding and fullScreenCover.
Main issue is that:
I choose identifiable item, corresponding sheet appears
I close that sheet with swipe
After it's dismiss i open normal sheet
It open again identifiable item sheet content
Another weird behaviour is if I open fullScreenCover after dismissing sheet it's open sheet but with content of fullScreenCover
This never happens on iOS 14. I think that is states that responsible for presenting those views not have time to update
You can checkout minimal gist reproduction or see it here
import SwiftUI
struct ContentView: View {
enum Sheet: String, Identifiable {
var id: String {
rawValue
}
case one, two
}
#State var isFullScreenPresented: Bool = false
#State var isSheetPresented: Bool = false
#State var isItemSheetPresented: Sheet?
var body: some View {
VStack {
HStack {
Button(action: {isFullScreenPresented.toggle()}, label: {
Text("fullScreen")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isSheetPresented.toggle()}, label: {
Text("sheet")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Button(action: {isItemSheetPresented = .one}, label: {
Text("one")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isItemSheetPresented = .two}, label: {
Text("two")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Text(isFullScreenPresented.description)
Text(isSheetPresented.description)
}
Text(isItemSheetPresented.debugDescription)
Spacer()
}
.sheet(item: $isItemSheetPresented, onDismiss: {isItemSheetPresented = nil}, content: {item in
Text(item.id)
})
.sheet(isPresented: $isSheetPresented, onDismiss: { isSheetPresented = false}, content: {
Text("sheet")
})
.fullScreenCover(isPresented: $isFullScreenPresented, onDismiss: {isFullScreenPresented = false }, content: {FullScreenContent()})
}
}
struct FullScreenContent: View {
#Environment(\.presentationMode) var dismiss
var body: some View {
VStack {
Button("close", action: {dismiss.wrappedValue.dismiss()})
Spacer()
}
}
}

Binding two ForEach loop to update each item cell

This is my second post and I need your help as much as possible. I am creating a favorite button on my parent view and detail view. I need both buttons to work correspondent to each other. When I marked favorite on the ForEach loop of my parent view, I want to show the item is favorited in my detail view. Also, I can unfavorite or favorite from my detail view vice vasa. It is really hard for me to figure out how to bind those two ForEach loops. Below I provide an example of my codes. If you want to test with my full code, you can access it here: Making favorite button from several layers and binding two list using EnvironmentObject
struct Data: Identifiable {
let id = UUID()
let number: Int
var name1: String
let name2: String
}
public struct DataList {
static var dot = [
Data(number: 1,
name1: "Pasian Phatna",
name2: "Praise God, from whom All Blessings Flow"),
Data(number: 2,
name1: "Itna Kumpi, Ka Tuu-Cing Pa",
name2: "The King of Love My Shephaerd Is (Dominus Regit Me)"),
Data(number: 3,
name1: "Kumpipa Bia Un",
name2: "O Worship the King"),
Data(number: 4,
name1: "Pa Tung Min Than'na Om Hen",
name2: "Gloria Patri (1st Tune)"),
Data(number: 5,
name1: "Pa Tung Min Than'na Om Hen",
name2: "Gloria Patri (2nd Tune)")
]
}
struct ParentView: View {
#State var datas: [Data] = DataList.dot
var body: some View {
NavigationView {
ScrollView (.vertical, showsIndicators: false) {
LazyVStack(spacing: 5) {
ForEach (datas, id: \.id) { data in
MainData(data: data)
Divider()
.padding(.all)
}
}
}
.navigationBarHidden(true)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct MainData: View {
#State var data: Data
#State var selectedFavoriteSong: Bool = false
var body: some View {
HStack {
Button(action: {
self.selectedFavoriteSong.toggle()
}, label: {
if selectedFavoriteSong {
Image(systemName: "suit.heart.fill")
.foregroundColor(.red)
.padding(.horizontal)
} else {
Image(systemName: "suit.heart")
.padding(.horizontal)
}
})
Spacer()
Text("\(data.number)")
Spacer()
}
.padding(.top)
VStack {
Text(data.name1)
.font(.title2.smallCaps())
.fontWeight(.bold)
.foregroundColor(.primary)
Text(data.name2)
.font(.title3)
.fontWeight(.medium)
.foregroundColor(.secondary)
.italic()
}
.padding(.horizontal)
.multilineTextAlignment(.center)
}
}
Please consider, the Search() below will pop up when I tapped the search icon (which is not presented here). My point is the Search() is not directly connect to the ParentView() but the DetailView() is embedded in the Search().
struct Search: View {
#State var datas: [Data] = DataList.dot
var body: some View {
NavigationView {
ScrollView (.vertical, showsIndicators: false) {
LazyVStack(alignment: .leading, spacing: 10) {
ForEach (datas, id: \.id) { data in
NavigationLink(
destination: DetailView(data: data),
label: {
Text("Search")
})
}
}.padding(.horizontal)
}
}
}
}
struct DetailView: View {
#State var data: Data
#State var selectedFavoriteSong: Bool = false
var body: some View {
HStack {
Button(action: {
self.selectedFavoriteSong.toggle()
}, label: {
if selectedFavoriteSong {
Image(systemName: "suit.heart.fill")
.foregroundColor(.red)
.padding(.horizontal)
} else {
Image(systemName: "suit.heart")
.padding(.horizontal)
}
})
Spacer()
Text("\(data.name1)")
Spacer()
}
.padding(.top)
VStack {
Text(data.name2)
.font(.title2.smallCaps())
.fontWeight(.bold)
.foregroundColor(.primary)
}
.padding(.horizontal)
.multilineTextAlignment(.center)
Spacer()
}
}
So, I want to connect the parent view and the detail view with some kind of binding property. But there is impossible to connect these two. I can store
#State var selectedFavoriteSong: Bool = false
inside the EnvironmentObject. But when I click favorite, all the items inside the ForEach loop are selected. Please help me on this issue. If you need a full code, the above link will direct to my first post. Thank you.
I'd suggest storing all of your data in an ObservableObject that is owned by the parent view and then can get passed into subviews (either explicitly or via an EnvironmentObject):
class DataSource : ObservableObject {
#Published var data : [Data] = DataList.dot
#Published var favoritedItems: Set<UUID> = []
func favoriteBinding(forID id: UUID) -> Binding<Bool> {
.init {
self.favoritedItems.contains(id)
} set: { newValue in
if newValue {
self.favoritedItems.insert(id)
} else {
self.favoritedItems.remove(id)
}
}
}
}
For example:
struct ParentView : View {
#StateObject var dataSource = DataSource()
var body: some View {
VStack {
Search(dataSource: dataSource)
}
}
}
Note that the data source stores a list of IDs that have been favorited. It uses a custom binding that can pass the boolean value down to a detail view:
struct Search: View {
#ObservedObject var dataSource : DataSource
var body: some View {
NavigationView {
ScrollView (.vertical, showsIndicators: false) {
LazyVStack(alignment: .leading, spacing: 10) {
ForEach (dataSource.data, id: \.id) { data in
NavigationLink(
destination: DetailView(data: data,
selectedFavoriteSong: dataSource.favoriteBinding(forID: data.id)),
label: {
Text(data.name1)
})
}
}.padding(.horizontal)
}
}
}
}
struct DetailView: View {
var data : Data
#Binding var selectedFavoriteSong : Bool
var body: some View {
HStack {
Button(action: {
self.selectedFavoriteSong.toggle()
}, label: {
if self.selectedFavoriteSong {
Image(systemName: "suit.heart.fill")
.foregroundColor(.red)
.padding(.horizontal)
} else {
Image(systemName: "suit.heart")
.padding(.horizontal)
}
})
Spacer()
Text("\(data.name1)")
Spacer()
}
.padding(.top)
VStack {
Text(data.name2 ?? "")
.font(.title2.smallCaps())
.fontWeight(.bold)
.foregroundColor(.primary)
}
.padding(.horizontal)
.multilineTextAlignment(.center)
Spacer()
}
}

SwiftUI Tab Selection Not Working With Any Hashable Content

SwiftUI’s Tab selection is suppose to work with any hashable content however that doesn’t seem to work.
In the example provided, you can see that in “Working” Tab, eveything works correctly if you use an integer for the tab selection. When you switch over to the “Broken” tab, the selection is a ColorItem and the selection does not update the view.
I believe this is a SwiftUI bug and have filed a feedback(FB8879981).
Tested with Xcode 12.2 and iOS 14.2(RC).
struct ColorItem: Identifiable, Hashable{
let color: Color
let title: String
var id: String{
title
}
}
struct ContentView: View {
let items = [
ColorItem(color: .red, title: "Red"),
ColorItem(color: .blue, title: "Blue"),
ColorItem(color: .purple, title: "Purple")
]
var body: some View {
TabView{
TabViewWorking(items: items)
.tabItem {
Label("Working", systemImage: "hand.thumbsup")
}
TabViewBroken(items: items)
.tabItem {
Label("Broken", systemImage: "hand.thumbsdown")
}
}
}
}
struct TabViewWorking: View {
#State private var tabSelection = 0
let items: [ColorItem]
var body: some View {
ZStack{
TabView(selection: $tabSelection){
ForEach(0..<items.count){ i in
items[i].color.edgesIgnoringSafeArea(.all)
.tag(i)
}
}
.tabViewStyle(PageTabViewStyle())
VStack{
Text(tabSelection.description)
Text(items[tabSelection].title)
}
.font(.largeTitle)
}
}
}
struct TabViewBroken: View {
#State private var tabSelection = ColorItem(color: .red, title: "Red")
let items: [ColorItem]
var body: some View {
ZStack{
TabView(selection: $tabSelection){
ForEach(0..<items.count){ i in
items[i].color.edgesIgnoringSafeArea(.all)
.tag(i)
}
}
.tabViewStyle(PageTabViewStyle())
VStack{
Text(items.firstIndex(of: tabSelection)?.description ?? "N/A")
Text(tabSelection.title)
}
.font(.largeTitle)
}
}
}
No, it is not SwiftUI bug. Type of selection and type of tag must be same, so in your first scenario they are both integers, but in second one they are not same - selection is ColorItem, but tag is still integer - thus selection does not work.
Here is fixed variant:
struct TabViewBroken: View {
#State private var tabSelection = ColorItem(color: .red, title: "Red")
let items: [ColorItem]
var body: some View {
ZStack{
TabView(selection: $tabSelection){
ForEach(0..<items.count){ i in
items[i].color.edgesIgnoringSafeArea(.all)
.tag(items[i]) // << here !!
}
}
.tabViewStyle(PageTabViewStyle())
VStack{
Text(items.firstIndex(of: tabSelection)?.description ?? "N/A")
Text(tabSelection.title)
}
.font(.largeTitle)
}
}
}