Segmented style picker alignment in NavigationBarItems - ios

I'm now working on putting segmented style picker inside NavigationBarItems.
Here's the code I'm using:
struct ContentView: View {
let modes = ["temperature", "distance"]
var body: some View {
NavigationView {
ZStack {
...
}
}
.navigationBarItems (leading:
Picker ("Select mode:", selection: $currentMode) {
ForEach (0..<mods.count) {
Text(self.mods[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
)
}
}
}
If I use leading: the picker is shown on the left, if I use trailing: then the picker is shown on the right. How can I put it in the centre?

Use .toolbar instead, like
ZStack {
Text("Demo")
}
.toolbar {
ToolbarItem(placement: .principal) {
Picker ("Select mode:", selection: $currentMode) {
ForEach (0..<modes.count) {
Text(self.modes[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}

Related

SwiftUI - Navigation Link pops out on iPhone, but not in Simulator

I have an app that contains several views with NavigationLinks inside.
The main view looks like this, calling a Toolbar view I have created.
struct CountListView: View {
#StateObject var vm = CountListViewModel()
let navigationBar = HomePageNavigationBar()
var body: some View {
NavigationView {
List {
ForEach(vm.count, id: \.uid) { item in
NavigationLink(destination: CountView(count: item)) {
CountListItemView(name: item.name)
}
}
}
.toolbar {
navigationBar.rightSideOfBar()
navigationBar.leftSideOfBar()
}
.navigationBarTitle("Count")
The navigation bar function that is playing up looks like this
func leftSideOfBar() -> some ToolbarContent {
ToolbarItemGroup(placement: .navigationBarLeading) {
NavigationLink(destination: SettingsView()) {
Label("Settings", systemImage: "gear")
}
}
}
And the SettingsView is as follows:
struct SettingsView: View {
var body: some View {
List {
NavigationLink(destination: NameSettingView()) {
Text("Name")
}
.buttonStyle(PlainButtonStyle())
NavigationLink(destination: PrivacyPolicyView()) {
Text("Privacy Policy")
}
.buttonStyle(PlainButtonStyle())
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
When I open the Privacy Policy View on device, the returns to the SettingsView without any user intervention. But this problem doesn't exist in the simulator.

Navigation bar title is stuck

If I use a toolbar for the keyboard which has a ScrollView inside it messes up the navigation bar title which will just be positioned stuck at the screen instead of moving in the navigation bar.
Does anyone have a solution for this issue?
(Xcode 13.4.1)
Minimal reproducible code:
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var numbers = Array(1...100).map { String($0) }
var body: some View {
NavigationView {
List($numbers, id: \.self) { $number in
TextField("", text: $number)
}
.toolbar {
ToolbarItem(placement: .keyboard) {
ScrollView(.horizontal) {
HStack {
Text("Hello")
Text("World")
}
}
}
}
.navigationTitle("Messed up title")
}
}
}
It seems like you are trying to add 100 toolbar item elements inside your keyboard which is causing performance issue and impacting on your navigation bar which could be issue in lower Xcode version compatibility. if you want to show 100 toolbar item elements then instead of adding inside keyboard add separate View and add on top of it and then based on keyboard appear or disappear hide/show 100 elements view accordingly. So I modify your code which is adding two toolbar items elements inside your keyboard and that seems to be working fine without any navigation title stuck issue eg below:-
var body: some View {
NavigationView {
List($numbers, id: \.self) { $number in
TextField("", text: $number)
}.toolbar {
ToolbarItem(placement: .keyboard) {
HStack {
Button("Cancel") {
print("Pressed")
}
Spacer()
Button("Done") {
print("Pressed")
}
}
}
}.navigationTitle("Messed up title")
}
}
Edited Answer if you want to use ScrollView, instead of using List use ScrollView like below
Please note this changes are required only if you are using lower Xcode version prior than Xcode 14
var body: some View {
NavigationView {
ScrollView {
ForEach($numbers, id: \.self) { number in
VStack {
TextField("", text: number)
}
}
}.toolbar {
ToolbarItem(placement: .keyboard) {
ScrollView(.horizontal) {
HStack {
Text("Hello")
Text("World")
}
}
}
}.navigationTitle("Messed up title")
}
}

SwiftUI - Navigation View title is overlapping list

I have a simple list with a navigation view, where the navigation view is overlapping the list while scrolling.
Here is the look I want.
Here is what I am getting with the overlap
Here is the code
struct MedicalDashboard: View {
let menuItemData = MenuItemList()
var body: some View {
NavigationView {
List(menuItemData.items, id: \.id) { item in
MenuItemRow(menuItem: item)
}
.listStyle(.insetGrouped)
.navigationTitle("Dashboard")
.navigationBarItems(trailing:
Button(action: {
// TODO: - Pop up a sheet for the settings page.
print("User icon pressed...")
}) {
Image(systemName: "person.crop.circle").imageScale(.large)
}
)
.padding(.top)
}
}
}
when I add padding(.top) the overlap stops but I get a different color background on the navigation
On Xcode 13.4, except a missing }, without the .padding(.top) and with a custom List everything works like a charm for me.
The problem might come from MenuItemList().
I have still updated your code by replacing .navigationBarItems and by adding the sheet for you:
struct MedicalDashboard: View {
#State private var showSheet = false
var body: some View {
NavigationView {
List { // Custom list
Text("Hello")
Text("Salut")
}
.listStyle(.insetGrouped)
.navigationTitle("Dashboard")
.toolbar() { // .navigationBarItems will be deprecated
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showSheet.toggle()
print("User icon pressed.")
}, label: {
Image(systemName: "person.crop.circle")
})
.sheet(isPresented: $showSheet) { /* SettingsView() */ }
}
}
}
} // New
}
Edit your post and show us MenuItemList().
Try this:
Swift
struct MedicalDashboard: View {
init() {
if #available(iOS 15, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
}
...
}

How to get rid of layer that appears on top of navigation bar when list comes under navigation bar?

I want to set navigation view be always black, even when list comes under it. Right now the navigation view becomes gray in this case. I want to change color of navigation view dynamically, that's why I used NavigationBarModifier, but as this is not connected to this question I deleted functions that do that. Here's the current code.
import SwiftUI
struct NavigationBarModifier: ViewModifier {
var backgroundColor: Binding<Color>
init(backgroundColor: Binding<Color>) {
self.backgroundColor = backgroundColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
self.backgroundColor.wrappedValue
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(_ bgColor: Binding<Color>) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: bgColor))
}
}
struct ContentView: View {
#State private var bgColor: Color = .black
var body: some View {
NavigationView {
List {
Button {
} label: {
Text("Button")
}
Button {
} label: {
Text("Button")
}
Button {
} label: {
Text("Button")
}
} .navigationBarColor(self.$bgColor)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here's how it looks now:
This is default behavior of navigation bar (inline display mode for List), you can disable navigation bar to get rid of it
NavigationView {
List {
Button {
} label: {
Text("Button")
}
Button {
} label: {
Text("Button")
}
Button {
} label: {
Text("Button")
}
}
.navigationBarHidden(true) // << here !!
.navigationBarColor(self.$bgColor)
}

Presenting a modal view sheet from a Sub view

I am trying to present a sheet from a sub view selected from the menu item on the navigation bar but the modal Sheet does does not display. I spent a few days trying to debug but could not pin point the problem.
I am sorry, this is a little confusing and will show a simplified version of the code to reproduce. But in a nutshell, the problem seems to be a sheet view that I have as part of the main view. Removing the sheet code from the main view displays the sheet from the sub view. Unfortunately, I don't have the freedom to change the Mainview.swift
Let me show some code to make it easy to understand....
First, before showing the code, the steps to repeat the problem:
click on the circle with 3 dots in the navigation bar
select the second item (Subview)
click on the "Edit Parameters" button and the EditParameters() view will not display
ContentView.swift (just calls the Mainview()). Included code to copy for reproducing issue :-)
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Mainview()
}
}
}
}
Mainview.swift. This is a simplified version of the actual App which is quite complex and I don't have leeway to change much here unfortunately!
fileprivate enum CurrentView {
case summary, sub
}
enum OptionSheet: Identifiable {
var id: Self {self}
case add
}
struct Mainview: View {
#State private var currentView: CurrentView? = .summary
#State private var showSheet: OptionSheet? = nil
var body: some View {
GeometryReader { g in
content.frame(width: g.size.width, height: g.size.height)
.navigationBarTitle("Main", displayMode: .inline)
}
//Removing the below sheet view will display the sheet from the subview but with this sheet here, it the sheet from subview does not work. This is required as these action items are accessed from the second menu item (circle and arrow) navigation baritem
.sheet(item: $showSheet, content: { mode in
sheetContent(for: mode)
})
.toolbar {
HStack {
trailingBarItems
actionItems
}
}
}
var actionItems: some View {
Menu {
Button(action: {
showSheet = .add
}) {
Label("Add Elements", systemImage: "plus")
}
} label: {
Image(systemName: "cursorarrow.click").resizable()
}
}
var trailingBarItems: some View {
Menu {
Button(action: {currentView = .summary}) {
Label("Summary", systemImage: "list.bullet.rectangle")
}
Button(action: {currentView = .sub}) {
Label("Subview", systemImage: "circle")
}
} label: {
Image(systemName: "ellipsis.circle").resizable()
}
}
#ViewBuilder
func sheetContent(for mode: OptionSheet) -> some View {
switch mode {
case .add:
AddElements()
}
}
#ViewBuilder
var content: some View {
if let currentView = currentView {
switch currentView {
case .summary:
SummaryView()
case .sub:
SubView()
}
}
}
}
Subview.swift. This is the file that contains the button "Edit Parameters" which does not display the sheet. I am trying to display the sheet from this view.
struct SubView: View {
#State private var editParameters: Bool = false
var body: some View {
VStack {
Button(action: {
editParameters.toggle()
}, label: {
HStack {
Image(systemName: "square.and.pencil")
.font(.headline)
Text("Edit Parameters")
.fontWeight(.semibold)
.font(.headline)
}
})
.padding(10)
.foregroundColor(Color.white)
.background(Color(.systemBlue))
.cornerRadius(20)
.sheet(isPresented: $editParameters, content: {
EditParameterView()
})
.padding()
Text("Subview....")
}
.padding()
}
}
EditParameters.swift. This is the view it should display when the Edit Parameters button is pressed
struct EditParameterView: View {
var body: some View {
Text("Edit Parameters...")
}
}
Summaryview.swift. Nothing special here. just including for completeness
struct SummaryView: View {
var body: some View {
Text("Summary View")
}
}
In SwiftUI, you can't have 2 .sheet() modifiers on the same hierarchy. Here, the first .sheet() modifier is on one of the parent views to the second .sheet(). The easy solution is to move one of the .sheets() so it's own hierarchy.
You could either use ZStacks:
var body: some View {
ZStack {
GeometryReader { g in
content.frame(width: g.size.width, height: g.size.height)
.navigationBarTitle("Main", displayMode: .inline)
}
ZStack{ }
.sheet(item: $showSheet, content: { mode in
sheetContent(for: mode)
})
}
.toolbar {
HStack {
trailingBarItems
actionItems
}
}
}
or more elegantly:
var body: some View {
GeometryReader { g in
content.frame(width: g.size.width, height: g.size.height)
.navigationBarTitle("Main", displayMode: .inline)
}
.background(
ZStack{ }
.sheet(item: $showSheet, content: { mode in
sheetContent(for: mode)
})
)
.toolbar {
HStack {
trailingBarItems
actionItems
}
}
}

Resources