Добавление сегментированного средства выбора стилей в NavigationView SwiftUI

Вопрос такой же простой, как и в заголовке. Я пытаюсь поместить Picker, который имеет стиль от SegmentedPickerStyle до NavigationBar в SwiftUI. Это похоже на страницу истории родного Phone приложения. Изображение ниже

введите описание изображения здесь

Я искал Google и Github, например, проекты, библиотеки или любые учебные пособия, и не повезло. Я думаю, что если в нативных приложениях и, например, в WhatsApp он есть, то это должно быть возможно. Любая помощь будет оценена по достоинству.


person Faruk    schedule 02.02.2020    source источник


Ответы (5)


Вы можете поместить средство выбора непосредственно в .navigationBarItems.

введите описание изображения здесь

Единственная проблема, с которой я столкнулся, - это центрировать сборщик. (Чтобы показать, что Picker действительно может находиться на панели навигации, я собрал своего рода хакерское решение с рамкой и Geometry 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[$0])
                            }
                        }
                        .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)
        }
    }
}
person Fry    schedule 03.02.2020
comment
@Faruk сделал хороший ответ для центрирования сборщика: stackoverflow.com/a/60510555/8642838 - person Jonas Deichelmann; 06.05.2020

Панель инструментов 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())
                }
            }
    }
}
person hstdt    schedule 20.07.2020
comment
Идеально! Используйте .principal для верхнего центра, используйте .bottomBar для чего-то более доступного. - person Othyn; 04.10.2020

Для тех, кто хочет сделать это мертвой точкой, просто поместите два 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[$0])
        }
     }
    .pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())

ОБНОВИТЬ

Возникла проблема leading, и конечные элементы нарушали safeArea UINavigationBarContentView. Во время поиска я наткнулся на другое решение в этом ответе. Это небольшая вспомогательная библиотека под названием SwiftUIX. Если вы не хотите устанавливать всю библиотеку - как и я - я создал суть только для navigationBarItems. Просто добавьте файл в свой проект.

Но не забывайте об этом. Он растягивал Picker, чтобы покрыть все свободное пространство, и заставлял StatusView быть уже. Поэтому мне пришлось установить такие рамки;

.navigationBarItems(center:
    Picker(...) {
        ...
    }
    .frame(width: 150)
, trailing:
    StatusView()
    .frame(width: 70)
)
person Faruk    schedule 03.03.2020
comment
Это не сработает, для пояснения приведите пример рабочего решения. - person Matt; 24.04.2020

Если вам нужно, чтобы сегментный контроль был в центре, вам нужно использовать 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()
        }
    }
person zdravko zdravkin    schedule 22.09.2020

Простой ответ, как центрировать контроллер сегмента и скрыть одну из кнопок.

@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)
    }
person zdravko zdravkin    schedule 15.10.2020