将分段样式选择器添加到 SwiftUI 的 NavigationView
Adding Segmented Style Picker to SwiftUI's NavigationView
问题和标题一样简单。我正在尝试将具有 SegmentedPickerStyle
样式的 Picker
放置到 SwiftUI
中的 NavigationBar
。它就像本机 Phone
应用程序的历史页面。图片如下
我已经在 Google 和 Github 中查找项目、库或任何教程,但没有成功。我认为如果 nativa 应用程序和 WhatsApp 有它,那么它应该是可能的。任何帮助,将不胜感激。
您可以将Picker 直接放入.navigationBarItems 中。
我遇到的唯一麻烦是让选择器居中。 (只是为了表明选择器确实可以在导航栏中,我用框架和几何组合了一种 hacky 解决方案 Reader。你需要找到一个合适的居中解决方案。)
struct ContentView: View {
@State private var choices = ["All", "Missed"]
@State private var choice = 0
@State private var contacts = [("Anna Lisa Moreno", "9:40 AM"), ("Justin Shumaker", "9:35 AM")]
var body: some View {
GeometryReader { geometry in
NavigationView {
List {
ForEach(self.contacts, id: \.self.0) { (contact, time) in
ContactView(name: contact, time: time)
}
.onDelete(perform: self.deleteItems)
}
.navigationBarTitle("Recents")
.navigationBarItems(
leading:
HStack {
Button("Clear") {
// do stuff
}
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[[=10=]])
}
}
.frame(width: 130)
.pickerStyle(SegmentedPickerStyle())
.padding(.leading, (geometry.size.width / 2.0) - 130)
},
trailing: EditButton())
}
}
}
func deleteItems(at offsets: IndexSet) {
contacts.remove(atOffsets: offsets)
}
}
struct ContactView: View {
var name: String
var time: String
var body: some View {
HStack {
VStack {
Image(systemName: "phone.fill.arrow.up.right")
.font(.headline)
.foregroundColor(.secondary)
Text("")
}
VStack(alignment: .leading) {
Text(self.name)
.font(.headline)
Text("iPhone")
.foregroundColor(.secondary)
}
Spacer()
Text(self.time)
.foregroundColor(.secondary)
}
}
}
想要让它死点的人,只要在每边放两个HStack
,让它们宽度固定且相等。
将此方法添加到 View
扩展。
extension View {
func navigationBarItems<L, C, T>(leading: L, center: C, trailing: T) -> some View where L: View, C: View, T: View {
self.navigationBarItems(leading:
HStack{
HStack {
leading
}
.frame(width: 60, alignment: .leading)
Spacer()
HStack {
center
}
.frame(width: 300, alignment: .center)
Spacer()
HStack {
//Text("asdasd")
trailing
}
//.background(Color.blue)
.frame(width: 100, alignment: .trailing)
}
//.background(Color.yellow)
.frame(width: UIScreen.main.bounds.size.width-32)
)
}
}
现在您有一个 View
modifier
,其用法与 navigationBatItems(:_)
相同。您可以根据需要编辑代码。
用法示例:
.navigationBarItems(leading: EmptyView(), center:
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[[=11=]])
}
}
.pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())
UPDATE
存在 leading
的问题,尾随项违反了 UINavigationBarContentView
的 safeArea
。在搜索时,我在这个 answer. It is little helper library called SwiftUIX. If you do not want install whole library -like me- I created a gist 中遇到了另一个解决方案,仅供 navigationBarItems
使用。只需将文件添加到您的项目中。
但不要忘记这一点,它正在拉伸 Picker
以覆盖所有空闲 space 并迫使 StatusView
变窄。所以我不得不像这样设置框架;
.navigationBarItems(center:
Picker(...) {
...
}
.frame(width: 150)
, trailing:
StatusView()
.frame(width: 70)
)
SwiftUI 2 + 工具栏:
struct DemoView: View {
@State private var mode: Int = 0
var body: some View {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Color", selection: $mode) {
Text("Light").tag(0)
Text("Dark").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
如果您需要 segmentcontroll 居中,则需要使用 GeometryReader,下面的代码将提供选择器作为标题和尾随(右)按钮。
你在左右两边设置了两个相同宽度的视图,中间的视图将占据其余部分。
5 是一个神奇的数字,取决于您需要的段的宽度。
您可以进行试验,看看最适合您的。
GeometryReader {
Text("TEST")
.navigationBarItems(leading:
HStack {
Spacer().frame(width: geometry.size.width / 5)
Spacer()
picker
Spacer()
Button().frame(width: geometry.size.width / 5)
}.frame(width: geometry.size.width)
}
但更好的解决方案是,如果您保存选择器大小,然后计算其他帧大小,那么选择器在 ipad 和 iphone
上将相同
@State var segmentControllerWidth: CGFloat = 0
var body: some View {
HStack {
Spacer()
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
.background(Color.red)
segmentController
.fixedSize()
.background(PreferenceViewSetter())
profileButton
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
}
.onPreferenceChange(PreferenceViewKey.self) { preferences in
segmentControllerWidth = preferences.width
}
}
struct PreferenceViewSetter: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: PreferenceViewKey.self,
value: PreferenceViewData(width: geometry.size.width))
}
}
}
struct PreferenceViewData: Equatable {
let width: CGFloat
}
struct PreferenceViewKey: PreferenceKey {
typealias Value = PreferenceViewData
static var defaultValue = PreferenceViewData(width: 0)
static func reduce(value: inout PreferenceViewData, nextValue: () -> PreferenceViewData) {
value = nextValue()
}
}
简单回答如何将分段控制器居中并隐藏其中一个按钮。
@State var showLeadingButton = true
var body: some View {
HStack {
Button(action: {}, label: {"leading"})
.opacity(showLeadingButton ? true : false)
Spacer()
Picker(selection: $selectedStatus,
label: Text("SEGMENT") {
segmentValues
}
.id(UUID())
.pickerStyle(SegmentedPickerStyle())
.fixedSize()
Spacer()
Button(action: {}, label: {"trailing"})
}
.frame(width: UIScreen.main.bounds.width)
}
问题和标题一样简单。我正在尝试将具有 SegmentedPickerStyle
样式的 Picker
放置到 SwiftUI
中的 NavigationBar
。它就像本机 Phone
应用程序的历史页面。图片如下
我已经在 Google 和 Github 中查找项目、库或任何教程,但没有成功。我认为如果 nativa 应用程序和 WhatsApp 有它,那么它应该是可能的。任何帮助,将不胜感激。
您可以将Picker 直接放入.navigationBarItems 中。
我遇到的唯一麻烦是让选择器居中。 (只是为了表明选择器确实可以在导航栏中,我用框架和几何组合了一种 hacky 解决方案 Reader。你需要找到一个合适的居中解决方案。)
struct ContentView: View {
@State private var choices = ["All", "Missed"]
@State private var choice = 0
@State private var contacts = [("Anna Lisa Moreno", "9:40 AM"), ("Justin Shumaker", "9:35 AM")]
var body: some View {
GeometryReader { geometry in
NavigationView {
List {
ForEach(self.contacts, id: \.self.0) { (contact, time) in
ContactView(name: contact, time: time)
}
.onDelete(perform: self.deleteItems)
}
.navigationBarTitle("Recents")
.navigationBarItems(
leading:
HStack {
Button("Clear") {
// do stuff
}
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[[=10=]])
}
}
.frame(width: 130)
.pickerStyle(SegmentedPickerStyle())
.padding(.leading, (geometry.size.width / 2.0) - 130)
},
trailing: EditButton())
}
}
}
func deleteItems(at offsets: IndexSet) {
contacts.remove(atOffsets: offsets)
}
}
struct ContactView: View {
var name: String
var time: String
var body: some View {
HStack {
VStack {
Image(systemName: "phone.fill.arrow.up.right")
.font(.headline)
.foregroundColor(.secondary)
Text("")
}
VStack(alignment: .leading) {
Text(self.name)
.font(.headline)
Text("iPhone")
.foregroundColor(.secondary)
}
Spacer()
Text(self.time)
.foregroundColor(.secondary)
}
}
}
想要让它死点的人,只要在每边放两个HStack
,让它们宽度固定且相等。
将此方法添加到 View
扩展。
extension View {
func navigationBarItems<L, C, T>(leading: L, center: C, trailing: T) -> some View where L: View, C: View, T: View {
self.navigationBarItems(leading:
HStack{
HStack {
leading
}
.frame(width: 60, alignment: .leading)
Spacer()
HStack {
center
}
.frame(width: 300, alignment: .center)
Spacer()
HStack {
//Text("asdasd")
trailing
}
//.background(Color.blue)
.frame(width: 100, alignment: .trailing)
}
//.background(Color.yellow)
.frame(width: UIScreen.main.bounds.size.width-32)
)
}
}
现在您有一个 View
modifier
,其用法与 navigationBatItems(:_)
相同。您可以根据需要编辑代码。
用法示例:
.navigationBarItems(leading: EmptyView(), center:
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[[=11=]])
}
}
.pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())
UPDATE
存在 leading
的问题,尾随项违反了 UINavigationBarContentView
的 safeArea
。在搜索时,我在这个 answer. It is little helper library called SwiftUIX. If you do not want install whole library -like me- I created a gist 中遇到了另一个解决方案,仅供 navigationBarItems
使用。只需将文件添加到您的项目中。
但不要忘记这一点,它正在拉伸 Picker
以覆盖所有空闲 space 并迫使 StatusView
变窄。所以我不得不像这样设置框架;
.navigationBarItems(center:
Picker(...) {
...
}
.frame(width: 150)
, trailing:
StatusView()
.frame(width: 70)
)
SwiftUI 2 + 工具栏:
struct DemoView: View {
@State private var mode: Int = 0
var body: some View {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Color", selection: $mode) {
Text("Light").tag(0)
Text("Dark").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
如果您需要 segmentcontroll 居中,则需要使用 GeometryReader,下面的代码将提供选择器作为标题和尾随(右)按钮。
你在左右两边设置了两个相同宽度的视图,中间的视图将占据其余部分。 5 是一个神奇的数字,取决于您需要的段的宽度。 您可以进行试验,看看最适合您的。
GeometryReader {
Text("TEST")
.navigationBarItems(leading:
HStack {
Spacer().frame(width: geometry.size.width / 5)
Spacer()
picker
Spacer()
Button().frame(width: geometry.size.width / 5)
}.frame(width: geometry.size.width)
}
但更好的解决方案是,如果您保存选择器大小,然后计算其他帧大小,那么选择器在 ipad 和 iphone
上将相同 @State var segmentControllerWidth: CGFloat = 0
var body: some View {
HStack {
Spacer()
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
.background(Color.red)
segmentController
.fixedSize()
.background(PreferenceViewSetter())
profileButton
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
}
.onPreferenceChange(PreferenceViewKey.self) { preferences in
segmentControllerWidth = preferences.width
}
}
struct PreferenceViewSetter: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: PreferenceViewKey.self,
value: PreferenceViewData(width: geometry.size.width))
}
}
}
struct PreferenceViewData: Equatable {
let width: CGFloat
}
struct PreferenceViewKey: PreferenceKey {
typealias Value = PreferenceViewData
static var defaultValue = PreferenceViewData(width: 0)
static func reduce(value: inout PreferenceViewData, nextValue: () -> PreferenceViewData) {
value = nextValue()
}
}
简单回答如何将分段控制器居中并隐藏其中一个按钮。
@State var showLeadingButton = true
var body: some View {
HStack {
Button(action: {}, label: {"leading"})
.opacity(showLeadingButton ? true : false)
Spacer()
Picker(selection: $selectedStatus,
label: Text("SEGMENT") {
segmentValues
}
.id(UUID())
.pickerStyle(SegmentedPickerStyle())
.fixedSize()
Spacer()
Button(action: {}, label: {"trailing"})
}
.frame(width: UIScreen.main.bounds.width)
}