SwiftUI - custom TabBar height based on device - swift

I am trying to set up the height of my custom TabBar based on Device, my code:
struct MyTabBar: View {
#Binding var index: Int
var body: some View {
HStack {
Button(action: {
self.index = 0
}) {
Image(ImageText.iconHome.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 1
}) {
Image(ImageText.iconBell.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 2
}) {
Image(ImageText.iconAdd.image)
}
Spacer(minLength: 0)
Button(action: {
self.index = 3
}) {
Image(ImageText.iconSearch.image).foregroundColor(Color.red)
}
Spacer(minLength: 0)
Button(action: {
self.index = 4
}) {
Image(ImageText.iconHamburger.image)
}
}.padding(.horizontal, 26).frame(height: 56)
}
}
If the devices have notch, how can I set my custom TabBar to have higher height? Does SwiftUI have something that can be useful here?

You can use the GeometryReader element to access the safe area insets, and add it as a padding using .safeAreaInsets.bottom (note that the Xcode autocompletion is almost never working on GeometryProxy properties):
var body: some View {
GeometryReader { proxy in
VStack {
// Content goes here
Spacer()
// Custom tab bar
HStack {
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
Button(action: {}) {
Image(systemName: "house.fill")
.padding()
}
Spacer()
}
.padding(.bottom, proxy.safeAreaInsets.bottom)
.background(Color.red)
}
}.edgesIgnoringSafeArea(.bottom)
}

Related

How to prevent SwiftUI to rerender the entire tabview when using a Custom Alert view on top of it?

I have a tab view with 5 tabs. An alert view will be shown at the top when my view model's property gets true? But every time the alert is shown, the entire tab view is redrawn causing high cpu usage.
I want the alert to be shown regardless of which tab the user is in so I did a ZStack on the tab view to show the alert. But how do I prevent SwiftUI to redraw the entire tab view?
struct ContentView: View {
var body: some View {
ZStack {
TabView() {
MiniPlayer(content:
BrowseView()
)
.tabItem {
Group {
Image(systemName: "square.grid.2x2")
Text("Browse")
}
.onTapGesture {
selectedTab = 0
}
}
.tag(0)
MiniPlayer(content:
AllSongsListView()
)
.tabItem {
Group {
Image(systemName: "music.note")
Text("Songs")
}
.onTapGesture {
selectedTab = 1
}
}
.tag(1)
MiniPlayer(content:
PlaylistsView()
)
.tabItem {
Group {
Image(systemName: "music.note.list")
Text("Playlists")
}
.onTapGesture {
selectedTab = 2
}
}
.tag(2)
MiniPlayer(content:
MoreView()
)
.tabItem {
Group {
Image(systemName: "ellipsis")
Text("More")
}
.onTapGesture {
selectedTab = 3
}
}
.tag(3)
}
Alert()
}
.ignoresSafeArea(.keyboard)
}
}
struct Alert: View {
#EnvironmentObject var manager: Manager
var body: some View {
if manager.showSuccessAlert {
VStack {
VStack {
Text("Successful.")
.font(.system(size: 20, weight: .bold, design: .rounded))
.multilineTextAlignment(.center)
}
.frame(height: 50)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.background(Color.blue)
.clipShape(Capsule())
Spacer()
}
.zIndex(1)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
withAnimation {
manager.showSuccessAlert = false
}
}
}
.transition(.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .top)))
}
}
}
Try to move entire TabView into separated view, so that ZStack looks like
ZStack {
TabsContentView() // << move everything inside !!
Alert()
}

Boundary on top of NavigationView

When I was trying to change the color of my navigation view page I realized that there is a weird boundary on top. I can't figure out what it is or how to get rid of it. Would anyone happen to know?
Here is the code.
Image with displayMode: .inline
The parent view code is the view that is presenting the page I am having trouble with.
Parent View:
Code:
import Foundation
import SwiftUI
import UIKit
struct ContentView: View {
// variable for view model
#ObservedObject var viewModel = VariableViewModel()
// SWIFT UI START
var body: some View {
// Main page
NavigationView {
ZStack {
Color(.orange).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
HStack {
Spacer()
NavigationLink(destination:
SettingsView()
){
Image(systemName: "gearshape.fill").font(.system(size: 25))
}
Spacer()
Spacer()
Spacer()
Spacer()
Spacer()
Spacer()
NavigationLink(destination:
Text("You")
){
Image(systemName: "chart.bar.xaxis").font(.system(size: 25))
}
Spacer()
}
Text("Pick a mode!").font(.largeTitle).bold().offset(x: 0, y: 30)
ZStack {
VStack {
Spacer()
// ADDITION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Addition")
}
){
HStack {
Text("Addition")
Image(systemName: "plus.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
Spacer()
// SUBTRACTION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Subtraction")
}
){
HStack {
Text("Subtraction")
Image(systemName: "minus.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
Spacer()
// MULTIPLICATION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Multiplication")
}
){
HStack {
Text("Multiplication")
Image(systemName: "multiply.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
Spacer()
// DIVISION SECTION
NavigationLink(destination:
VStack {
Spacer()
MathView(operatorName: "Division")
}
){
HStack {
Text("Division")
Image(systemName: "divide.square")
}.font(.largeTitle)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.padding(10)
.border(Color.blue, width: 5)
}
}
}.navigationBarHidden(true)
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Code:
import SwiftUI
struct MathView: View {
#ObservedObject var viewModel = VariableViewModel()
let operatorName: String
var body: some View {
ZStack {
Color.orange.edgesIgnoringSafeArea(.all)
VStack {
Spacer()
NavigationLink(destination:
MathContentView(operatorName: "Addition", operatorSymbol: "plus", difficultyNumber1: 5, difficultyNumber2: 5)
) {
Text("Easy")
.font(.title2)
.padding(35)
.foregroundColor(.white)
.background(Color(.systemGreen))
.cornerRadius(40)
.onAppear(perform: {
if operatorName == "Addition" {
self.viewModel.result = self.viewModel.num1 + self.viewModel.num2
} else if operatorName == "Subtraction" {
self.viewModel.result = self.viewModel.num1 - self.viewModel.num2
} else if operatorName == "Multiplication" {
self.viewModel.result = self.viewModel.num1 * self.viewModel.num2
};
withAnimation {
viewModel.resetVariables()
// numbers generator
}
})
}
Spacer()
}
}
}
}
To remove the empty space below the NavigationView add .navigationBarTitleDisplayMode(.inline) to the top view:
ZStack {
// ...
}
.navigationBarTitleDisplayMode(.inline)
Then, the slim line between the navigationViewTitle and the content below comes from the Spacer at the top of the VStack in NavigationLink that pushes the MathView.
NavigationLink(destination:
VStack {
Spacer() // this causes the *slim line*
MathView(operatorName: "Addition")
}
)
You need to remove the Spacer (and the VStack as well):
NavigationLink(destination:
MathView(operatorName: "Addition")
)

Why is my SwiftUI VStack not aligning properly?

I'm trying to make my VStack padding consistent regardless of the content, and it doesn't seem to be working in certain instances (as if the Scrollview aligned with the center instead).
Ideally, I would like the padding to be consistent regardless of the content on the screen.
Such as this (when padding isn't consistent):
Here is the code used:
var body: some View {
VStack {
HStack{
Button(action: {}) {
Image(systemName: "arrow.backward")
}
Spacer(minLength: 0)
Button(action: {}) {
Image(systemName: "bookmark")
}
}
.padding(.vertical, 10)
ScrollView {
VStack{
HStack{
VStack (alignment: .leading, spacing: 5) {
Text("Today's Poem, \(currentDate)")
if let poem = fetch.poems.first {
Text("\(poem.title)")
Text("BY "+poem.author.uppercased())
VStack (alignment: .leading) {
ForEach(poem.lines, id: \.self) {
Text($0)
.multilineTextAlignment(.leading)
}
}
} else {
Spacer()
}
}
}
}
}
Button("Get Next Poem") { fetch.getPoem() }
}
.background(Color.white.ignoresSafeArea())
.padding(.horizontal)
}
}
Any ideas? Thanks in advance.
Change the innermost
VStack {
to
VStack(alignment: .leading) {

SwiftUI content is not displayed on top of the screen

I have a NavigationView and then a VStack. The issue is that everything inside VStack is being displayed in the middle of the screen and not on top. Why is that?
var body: some View {
VStack {
VStack(alignment: .leading) {
if (request?.receiverID ?? "") == userDataViewModel.userID {
Text("\(sendertFirstName) wants to ship your package").font(.title)
} else {
Text("You want to ship \(recieverFirstName)'s package").font(.title)
}
HStack{
Image(systemName: "clock.fill").font(.subheadline)
Text("\(createdAt, formatter: DateService().shortTime)").font(.subheadline).foregroundColor(.secondary)
}
HStack {
VStack(alignment: .leading) {
HStack {
Image(systemName: "person.fill").font(.subheadline)
Text(sendertFirstName).font(.subheadline).foregroundColor(.secondary)
}
}
Spacer()
VStack(alignment: .leading) {
HStack {
Image(systemName: "star.fill")
Text(rating).font(.subheadline).foregroundColor(.secondary)
}
}
}
}
VStack(alignment: .center) {
HStack {
Button("Accept") {
//print(self.request.createdAt)
//FirestoreService().respondToRequest(request: self.request.documentReference, status: "accepted")
}
.padding()
Button("Decline") {
//FirestoreService().respondToRequest(request: self.request.documentReference, status: "declined")
}
.foregroundColor(.red)
.padding()
}
}
ScrollView {
ForEach(messages) { (message: Message) in
if message.senderId == self.userDataViewModel.userID {
HStack {
Spacer()
Text(message.text).font(.subheadline).padding(7.5).background(self.blue).cornerRadius(30).foregroundColor(.white)
}.padding(.bottom, 2)
} else {
HStack {
Text(message.text).font(.subheadline).padding(7.5).background(self.gray).cornerRadius(30).foregroundColor(.white)
Spacer()
}.padding(.bottom, 2)
}
}
}
HStack {
TextField("Message", text: $inputMessage).padding(5).background(self.gray).cornerRadius(30).foregroundColor(.white)
Button(action: {
let msg: [String: Any] = [
"text": self.inputMessage,
"createdAt": Timestamp(),
"senderId": self.userDataViewModel.userID
]
self.reference.updateData([
"messages": FieldValue.arrayUnion([msg])
])
self.messages.append(Message(dictionary: msg))
self.inputMessage = ""
}) {
Image(systemName: "arrow.up.circle.fill").foregroundColor(self.blue).font(.title)
}
}
Spacer()
}
.padding()
.border(Color.red)
}
I want the content to start on top not on the middle. What am I doing wrong here?
You don't need the NavigationView - the way you have everything set up now you are wrapping the VStack in a NavigationView twice and the massive white space is the second empty navigation bar.
By default most Views take only the amount of space they need and they are placed in the center of their parent view. So if you want your content to be placed at the top, you need to add Spacer() as the last element in your VStack:
var body: some View {
VStack {
/* ... */
Spacer()
}
}
Spacer is one of the rare Views that takes all space offered to it by the parent view and will push all of your content upwards.
Maybe you can try this way:
NavigationView {
VStack {
...
}.frame(maxHeight:.infinity, alignment: .top)
.padding()
.border(Color.red).navigationBarTitle("", displayMode: .inline)
}
Add a .navigationBarHidden(true) to your VStack

Shortening swiftUI code: compiler is unable to type-check this expression in reasonable time

I am trying to create a UI in SwiftUI with two sets of ten buttons (Imaging a game of Cup Pong). Whenever I try to build or preview the code I get the following error message: 'The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions'. I was wondering how I could fix this.
I know that it is very long. Is there any way to fix it so that the code would work.
// ContentView.swift
// Text Pong
//
// Created by Thomas Braun on 8/21/19.
// Copyright © 2019 Thomas Braun. All rights reserved.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 250.0) {//Contains both the triangles
VStack {//User Triangle
HStack(spacing: 15.0) {
Button(action: {}) {
Text("7")
.font(.largeTitle)
}
Button(action: {}) {
Text("8")
.font(.largeTitle)
}
Button(action: {}) {
Text("9")
.font(.largeTitle)
}
Button(action: {}) {
Text("10")
.font(.largeTitle)
}
}
HStack(spacing: 15.0) {
Button(action: {}) {
Text("6")
.font(.largeTitle)
}
Button(action: {}) {
Text("5")
.font(.largeTitle)
}
Button(action: {}) {
Text("4")
.font(.largeTitle)
}
}
HStack(spacing: 15.0) {
Button(action: {}) {
Text("3")
.font(.largeTitle)
}
Button(action: {}) {
Text("2")
.font(.largeTitle)
}
}
HStack(spacing: 15.0) {
Button(action: {}) {
Text("1")
.font(.largeTitle)
}
}
}
// Text("Game On")
VStack {//Opponent Triangle
HStack {
VStack {
Button(action: {}) {
Text("1")
.font(.largeTitle)
}
HStack {
Button(action: {}) {
Text("2")
.font(.largeTitle)
}
Button(action: {}) {
Text("3")
.font(.largeTitle)
}
}
HStack {
Button(action: {}) {
Text("4")
.font(.largeTitle)
}
Button(action: {}) {
Text("5")
.font(.largeTitle)
}
Button(action: {}) {
Text("6")
.font(.largeTitle)
}
}
HStack {
Button(action: {}) {
Text("7")
.font(.largeTitle)
}
Button(action: {}) {
Text("8")
.font(.largeTitle)
}
Button(action: {}) {
Text("9")
.font(.largeTitle)
}
Button(action: {}) {
Text("10")
.font(.largeTitle)
}
}
}
}
}// Ending Opponent Triangle verticle Stack
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Break it into smaller parts. For example by each row and then by each player like this:
struct OpponentTriangleView: View {
var body: some View {
VStack {//Opponent Triangle
HStack {
VStack {
Part1View()
Part2View()
Part3View()
Part4View()
}
}
}// Ending Opponent Triangle vertical Stack
}
}
And define each part like this:
extension OpponentTriangleView {
struct Part1View: View {
var body: some View {
HStack {
Button(action: {}) { Text("1") .font(.largeTitle) }
}
}
}
struct Part2View: View {
var body: some View {
HStack {
Button(action: {}) { Text("2").font(.largeTitle) }
Button(action: {}) { Text("3").font(.largeTitle) }
}
}
}
struct Part3View: View {
var body: some View {
HStack {
Button(action: {}) { Text("4").font(.largeTitle) }
Button(action: {}) { Text("5").font(.largeTitle) }
Button(action: {}) { Text("6").font(.largeTitle) }
}
}
}
struct Part4View: View {
var body: some View {
HStack {
Button(action: {}) { Text("7").font(.largeTitle) }
Button(action: {}) { Text("8").font(.largeTitle) }
Button(action: {}) { Text("9").font(.largeTitle) }
Button(action: {}) { Text("10").font(.largeTitle) }
}
}
}
}
And similarly define UsertTriangleView. Then use them like this:
struct ContentView: View {
var body: some View {
VStack(spacing: 250.0) {//Contains both the triangles
UserTriangleView()
// Text("Game On")
OpponentTriangleView()
}
}
}
And you are good to go
- Notes:
Not only in SwiftUI, but always break huge codes into the smaller meaningful pieces.
Don`t Repeat Yourself. Try to create some builder function or use loops to achieve repeating tasks without actually writing it again and again.
I've seen Xcode (12.5.1) show this when I had a build error in my view. Try commenting out portions of your view and rebuilding, it will eventually show you what the real error is. In my case I was missing a constructor argument for one of my child views. After you find the real problem and fix it, you can uncomment everything and it should build fine now.
In my case a lot of the #State was the reason causing this error. After removing one it started to work