iOS 14 provided a new .toolbar implementation for the NavigationView. There is a placement parameter with options of .principal, .navigationBarLeading, and .navigationBarTrailing.
I'd like to add a search bar in the Navigation bar. What is the proper way to achieve a fullwidth search bar?
My expectation was that I would be able to put the search bar on the left using navigationBarLeading and set the frame to full width.
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
Text("Full width")
Spacer()
Text("Toolbar")
}.frame(width: .infinity)
}
}
I just had similar in the bottomBar. Maybe you can try. Cheers
ToolbarItem(placement: .navigationBarLeading){
Spacer()
}
ToolbarItem(placement: .principal){
HStack {
Text("Full width")
Spacer()
Text("Toolbar")
}
}
ToolbarItem(placement: .navigationBarTrailing){
Spacer()
}
Related
In the screenshot below, the purple image is not aligned horizontally with the globe symbol. I'm trying to center the navigation title with respect to the logo above it which I can totally do with a VStack. However I don't know how to align the logo itself with the navigation trailing item (globe in the screenshot). Any tips? ideas? tutorials? thank you!
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {} label: {Image(systemName: "globe")}
}
ToolbarItemGroup(placement: .principal) {
VStack {
Image("image")
.resizable()
.frame(width: 64.0, height: 24.0)
Text("Navigation Title")
}
}
}
}
.navigationBarTitleDisplayMode(.inline)
}
}
Just do same for leading toolbar item (because items are independent and one alignment does not affect other - only size of bar)
Tested with Xcode 13.4 / iOS 15.5
*borders are for better visibility
Text("Hello")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
VStack(alignment: .leading) {
Button {} label: {Image(systemName: "globe")}
.frame(height: 24.0)
Text("X").foregroundColor(.clear)
}.border(.red)
}
ToolbarItemGroup(placement: .principal) {
VStack {
Image("picture")
.resizable()
.frame(width: 64.0, height: 24.0)
Text("Navigation Title")
}.border(.green)
}
}
I have this TabView:
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)){
TabView(selection: $mainViewProperties.currentView) {
...
//HOME
NavigationView{
HomeView(
)
.navigationBarTitleDisplayMode(.inline)
}
.tag(MainViewProperties.Views.HOME)
...
}
//My Custom NavBar
CustomBottomNav(selectedTab: $mainViewProperties.currentView)
.padding()
}
And the HomeView's body is this:
List{
ForEach((0...30), id: \.self){ i in
HStack{
Spacer()
Text("New \(i)")
Spacer()
}
.padding()
.background(Color.backgroundOver)
.cornerRadius(.lotoUpCornerRadius)
.lotoUpShadow() // <- CUSTOM EXTENSION
//Bottom padding
if i == 30 {
VStack{
}
.frame(height: .paddingForBottomNav) // <- CUSTOM EXTENSION
}
}
.listRowSeparator(.hidden)
.listRowBackground(Color.background)
}
.listStyle(PlainListStyle())
.background(
Color.background
)
.toolbar {
ToolbarItem(placement: .principal){
Text(String.R.home_title)
.font(getLotoUpFont(size: 20))
.bold()
.foregroundWithPrimaryGradient() // <- CUSTOM EXTENSION
}
}
That look like this:
I don't want the side bar navigation on the iPad, so I added StackNavigationViewStyle to the NavigationView like this:
//HOME
NavigationView{
HomeView(
)
.navigationBarTitleDisplayMode(.inline)
}
.navigationViewStyle(.stack) //<- HERE
.tag(MainViewProperties.Views.HOME)
But then the Toolbar's background was not displayed anymore:
How can I get the Stack Navigation and the Toolbar's background get along together?
PD: I also tried this on HomeView:
.toolbar {
ToolbarItem(placement: .principal){
HStack{
Spacer()
Text(String.R.home_title)
.font(getLotoUpFont(size: 20))
.bold()
.foregroundWithPrimaryGradient()
Spacer()
}
.background(
Color.red
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
)
}
}
But it lets spaces that I don't want:
EDIT:
I tested this on a real device (iPhone 8 Plus) and worked fine, the NavigationBar was there, but on the simulated iPhone 8 plus the NavigationBar background was translucent.
So now I'm suspecting on the simulator. I don't have an iPad to test this out though.
It seems to be a bug on simulator with iOS 15. On iOS 15.2 it works just fine.
This is an example of what I am trying to do.Link to image of design to implement. I am unsure how to position a button and text at the top of the screen in this manner coding in swiftui. Alternatively I thought I could use the navigation bar inline and customise that but I am unsure.
var body: some View {
VStack (alignment: .trailing) {
HStack(spacing:10) {
Button(action: {
}) {
Image(systemName: "line.horizontal.3")
.font(.headline)
}
Text("WeCollab")
.foregroundColor(.white)
.font(.title)
//padding(.leading,40)
Spacer()
}
.padding(.top,UIApplication.shared.windows.first?.safeAreaInsets.top)
.background(customPurpleColour)
Spacer()
}
.edgesIgnoringSafeArea(.top)
}
}
Seems like you're on the right track. In order to keep the title centered, it seemed easier to make a ZStack. The menu button gets it's own .leading-aligned VStack and then the title goes on top of that.
The edgesIgnoringSafeArea and padding can be simplified so that you don't have to use the screen size safe areas.
struct ContentView: View {
var body: some View {
VStack {
ZStack {
VStack {
Button(action: { }) {
Image(systemName: "line.horizontal.3")
.font(.headline)
}.padding(.leading)
}.frame(maxWidth: .infinity, alignment: .leading)
Text("WeCollab")
.foregroundColor(.white)
.font(.title)
}
.background(Color.purple.edgesIgnoringSafeArea(.top))
Spacer() //other content goes here
}
}
}
When tapping on a NavigationLink, it reduces the opacity slightly. Is there a way to disable this. I tried using .buttonStyle(PlainButtonStyle()) but that didn't have the desired effect.
It is embedded in a scrollView (preferred over List for customizability):
ScrollView {
ForEach(items){ item in
NavigationLink(destination: DetailView()){
HStack{
Text("title")
Spacer()
Image(systemName: "chevron.right")
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10, style: continuous)
.foregroundColor(Color.gray)
)
}
}
}
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
Use custom button style that just returns label view (w/o highlight effect)
struct FlatLinkStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
}
}
and
NavigationLink(destination: DetailView()){
HStack{
Text("title")
Spacer()
Image(systemName: "chevron.right")
}
.padding()
}.buttonStyle(FlatLinkStyle()) // << here !!
I am experiencing a weird spacing behavior that I'm hoping someone can explain. I have two views, the main view (ContentView) contains a child view called PlayerToolbar. The desired behavior is for ContentView take up the entire screen with PlayerToolbar being rendered at the very bottom of the screen. PlayerToolbar contains image buttons and spacers. The issue I am running into is ContentView only takes up a portion of the screen and PlayerToolbar is not aligned to the bottom as shown in the image.
Here is the code for ContentView
struct ContentView: View {
var body: some View {
VStack{
Spacer()
Text("Main Content")
Spacer()
PlayerToolBar()
}.background(Color.blue)
}
}
And here is the code for PlayerToolbar:
struct PlayerToolBar: View {
var body: some View {
HStack{
Spacer()
Button(action: {
print("backward button pressed")
}){
Image(systemName: "gobackward.10").renderingMode(.original) .resizable().aspectRatio(contentMode: .fit)
}
Spacer()
Button(action: {
print("play button pressed")
}){
Image(systemName: "play.circle").renderingMode(.original) .resizable().aspectRatio(contentMode: .fit)
}
Spacer()
Button(action: {
print("go forward button pressed")
}){
Image(systemName: "goforward.10").renderingMode(.original) .resizable().aspectRatio(contentMode: .fit)
}
Spacer()
Button(action: {
print("jot button pressed")
}){
Image(systemName: "pencil.circle").renderingMode(.original) .resizable().aspectRatio(contentMode: .fit)
}
Spacer()
}.background(Color(UIColor.secondarySystemBackground))
}
}
I have found that if add one Text object in my PlayerToolbar between the first Spacer and Button, the screen renders as I expect
...
Spacer()
Text(" ")
Button(action: {
print("backward button pressed")
}){
Image(systemName: "gobackward.10").renderingMode(.original) .resizable().aspectRatio(contentMode: .fit)
}
...
Any idea of why it is behaving the way it is and why a Text makes it act the way I prefer?
The problem, I think, is because your PlayerToolbar has no height and it is hard for the layout logic to determine one. Nothing in your PlayerToolbar has an explicit height. Your images are made resizable, but nothing in your views is telling how to resize them.
By adding a Text() view, your images now have some height to match, and so it works as you expect it.
Other solutions to break the ambiguity are (choose any, not all):
Set a frame height to the PlayerToolbar:
PlayerToolBar().frame(height: 40)
Set the height for at least one of your images:
Image(systemName: "gobackward.10")
.renderingMode(.original)
.resizable()
.frame(height: 40)
.aspectRatio(contentMode: .fit)
Set the height to one of your buttons:
Button(action: {
print("backward button pressed")
}){
Image(systemName: "gobackward.10")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
}.frame(height: 40)
Set the height of the HStack in your PlayerToolbar.
Remove the resizable() modifier in at least one of your images.
Image(systemName: "pencil.circle").renderingMode(.original).aspectRatio(contentMode: .fit)
All these alternatives aim at the same thing, making sure your images know how much to grow/shrink. There are of course many other options. These are just a few.