Get that strange glitch and can't fix it.
Source of problem - TabView and changing size of child views with animation.
Changing UITabbar appearance not helped. Changing safe area options not helped.
UITabBar.appearance().isHidden = false and opaque appearance give flickering of whole tabbar.
I want to hide default UITabbar to customize my own.
Any ideas?
Flickering of bottom safe area: Demonstration GIF
Sample project:
struct ContentView: View {
#State var selected = "second"
var body: some View {
ZStack(alignment: .bottom) {
VStack(spacing: 0) {
FirstView()
TabView(selection: $selected) {
SecondView()
.tabItem({
Text("second")
})
.tag("second")
ThirdView()
.tabItem({
Text("third")
})
.tag("third")
}
}
HStack {
Image(systemName: selected == "second" ? "circle.fill" : "circle")
.onTapGesture {
selected = "second"
}
Image(systemName: selected == "third" ? "circle.fill" : "circle")
.onTapGesture {
selected = "third"
}
}
.frame(width: 70, height: 40, alignment: .center)
.background(Color.white)
.cornerRadius(10)
.padding()
}
.edgesIgnoringSafeArea(.all)
.onAppear {
UITabBar.appearance().isHidden = true
}
}
}
struct FirstView: View {
#State var height:CGFloat = 200
var body: some View {
ZStack(alignment: .bottom) {
Color.red
.frame(height: height)
Image(systemName: height == 200 ? "arrow.down" : "arrow.up")
.foregroundColor(.white)
.padding(5)
.onTapGesture {
withAnimation(.easeInOut(duration: 2)) {
height = height == 200 ? 350 : 200
}
}
}
}
}
struct SecondView: View {
var body: some View {
ZStack {
Text("hello")
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background(Color.green)
.onAppear {
UITabBar.appearance().isHidden = true
}
}
}
struct ThirdView: View {
var body: some View {
Color.blue
.edgesIgnoringSafeArea(.bottom)
}
}
Thanks for answers :)
Since you have your own index navigation anyways, I recommend to get rid of TabView altogether and switch views by if / else or switch statements:
struct ContentView: View {
#State var selected = "second"
var body: some View {
ZStack(alignment: .bottom) {
VStack(spacing: 0) {
FirstView()
if selected == "second" {
SecondView()
} else {
ThirdView()
}
}
HStack {
Image(systemName: selected == "second" ? "circle.fill" : "circle")
.onTapGesture {
selected = "second"
}
Image(systemName: selected == "third" ? "circle.fill" : "circle")
.onTapGesture {
selected = "third"
}
}
.frame(width: 70, height: 40, alignment: .center)
.background(Color.white)
.cornerRadius(10)
.padding()
}
.edgesIgnoringSafeArea(.all)
}
}
Related
I have a content in the safe area that reveals details on a button tap. I would like to allow the user to drag the handle to expand the content details as well, almost like the bottom sheet behaviour.
This is what I have so far:
struct ContentView: View {
#State private var isExpanded = false
var body: some View {
ScrollView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.frame(maxWidth: .infinity)
.padding()
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 24) {
Capsule()
.frame(width: 50, height: 5)
.opacity(0.5)
HStack(spacing: 16) {
Image(systemName: "list.bullet")
Spacer()
VStack {
Text("Track 2")
}
.font(.caption)
Spacer()
Image(systemName: "backward.fill")
Image(systemName: "play.fill")
Image(systemName: "forward.fill")
Spacer()
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
if isExpanded {
HStack {
Image(systemName: "airplayaudio")
Image(systemName: "timer")
Spacer()
Text("Artist")
}
}
}
.padding([.bottom, .horizontal])
.padding(.top, 8)
.background(.ultraThinMaterial)
}
}
}
How can I add functionality to the Capsule to drag the handle to expand that animates in a way to stretch the content in an elastic way (not just swipe to expand, but to drag the content until the user let's go)?
You can use a DragGesture on your Capsule:
.gesture(
DragGesture()
.onEnded { value in
withAnimation(.spring()) {
let swipingUp = value.translation.height < 0
withAnimation {
isExpanded = swipingUp ? true: false
}
}
}
)
To allow for a stretch, you can use onChanged paired with a State variable:
struct ContentView: View {
#State private var isExpanded = false
#State private var stretch = CGFloat.zero
var body: some View {
ScrollView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.frame(maxWidth: .infinity)
.padding()
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 24) {
Capsule()
.frame(width: 50, height: 5)
.opacity(0.5)
.gesture(
DragGesture()
.onChanged { newValue in
withAnimation(.spring()) {
stretch = abs(newValue.translation.height)
}
}.onEnded { value in
withAnimation(.spring()) {
isExpanded = value.translation.height < 0
stretch = .zero
}
}
)
HStack(spacing: 16) {
Image(systemName: "list.bullet")
Spacer()
VStack {
Text("Track 2")
}
.font(.caption)
Spacer()
Image(systemName: "backward.fill")
Image(systemName: "play.fill")
Image(systemName: "forward.fill")
Spacer()
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
if isExpanded {
HStack {
Image(systemName: "airplayaudio")
Image(systemName: "timer")
Spacer()
Text("Artist")
}
}
if stretch > 0 && !isExpanded {
Color.clear
.frame(height: stretch)
}
}.padding([.bottom, .horizontal])
.padding(.top, 8)
.background(.ultraThinMaterial)
}
}
}
I have NavigationView in ContentView
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
BarView()
AdsView()
SelectTypeVideo()
Spacer()
}
.ignoresSafeArea(.all)
}
}
}
I can't use it either in a sub-struct like BarView()
I tried to use the first
if isSearch {
SearchBar()
}
or
.onTapGesture {
isSearch.toggle()
}
Doesn't respond when pressed
I want to move from the BarView() to the SearchBar()
struct BarView: View {
#State var isSearch = false
var body: some View {
if isSearch {
SearchBar()
}
HStack {
ZStack {
HStack {
Button(action: { isSearch.toggle() }) {
ZStack {
Rectangle()
.fill(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 10)
.frame(maxWidth: 35, maxHeight: 35)
.padding(.top, 25)
Image(systemName: "gear")
.padding(.top, 25).padding()
.foregroundColor(Color.black)
}
}
}
}
Spacer()
Text("other")
.padding(.top, 30).padding()
.font(.title)
}.padding(.top, 10)
}
}
Sorry if the question is simple, but I didn't find a solution
Remove
if isSearch {
SearchBar()
}
from BarView and place it in ContentView like this
if isSearch {
SearchBar()
} else {
BarView()
}
I have a header that is fixed in place using an offset relative to the scroll position. Strangely enough though, when the contents of the scroll view has a dynamic opacity to the buttons, the offset is very jumpy:
This is the scroll view code, the HeaderView is "fixed" in place by pinning the offset to the scroll view's offset. The opacity seems to be causing the performance issue is on the MyButtonStyle style on the last line of code:
struct ContentView: View {
#State private var isPresented = false
#State private var offsetY: CGFloat = 0
#State private var headerHeight: CGFloat = 200
var body: some View {
GeometryReader { screenGeometry in
ZStack {
Color(.label)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 0.0) {
Color.clear
.frame(height: headerHeight)
.overlay(
HeaderView(isPresented: $isPresented)
.offset(y: offsetY != 0 ? headerHeight + screenGeometry.safeAreaInsets.top - offsetY : 0)
)
VStack(spacing: 16) {
VStack(alignment: .leading) {
ForEach(0...10, id: \.self) { index in
Button("Button \(index)") {}
.buttonStyle(MyButtonStyle(icon: Image(systemName: "alarm")))
}
}
Spacer()
}
.frame(maxWidth: .infinity, minHeight: screenGeometry.size.height)
.padding()
.background(
GeometryReader { geometry in
Color.white
.cornerRadius(32)
.onChange(of: geometry.frame(in: .global).minY) { newValue in
offsetY = newValue
}
}
)
}
}
}
.alert(isPresented: $isPresented) { Alert(title: Text("Button tapped")) }
}
}
}
struct HeaderView: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
Image(systemName: "bell")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(Color(.systemBackground))
Button(action: { isPresented = false }) {
Text("Press")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(16)
}
}
.padding()
}
}
struct MyButtonStyle: ButtonStyle {
let icon: Image
func makeBody(configuration: Configuration) -> some View {
Content(
configuration: configuration,
icon: icon
)
}
struct Content: View {
let configuration: Configuration
let icon: Image
var body: some View {
HStack(spacing: 18) {
Label(
title: { configuration.label },
icon: { icon.padding(.trailing, 8) }
)
Spacer()
Image(systemName: "chevron.right")
.accessibilityHidden(true)
}
.padding(18)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(8)
.opacity(configuration.isPressed ? 0.5 : 1) // <-- Comment this out and jumpiness goes away!!
}
}
}
Is there a performance improvement that can be done to use the opacity on the button press and make the jumpiness go away? Or a different way to approach this sticky offset because not sure if this is actually the source of the issue and I use opacity in a lot of places in my app (not just button presses)? The purpose of doing it this way is so the button can be tapped instead of putting it in the background of the scroll view. Thanks for any help or insight!
OUTLINE
I have made a custom slimline sidebar that I am now implementing across the whole app. The sidebar consists of a main button that is always showing and when pressed it shows or hides the rest of the sidebar that consists of buttons navigating to other views.
I am currently implementing the sidebar across the app on each view by creating a ZStack like this:
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
SideBarCustom()
Text("Hello, World!")
}
}
}
PROBLEM
I am planning on adding a GeometryReader so if the side bar is shown the rest of the content moves over. With this in mind, the way I am implementing the sidebar on every view feels clunky and a long winded way to add it. Is there a more simple/better method to add this to each view?
Sidebar Code:
struct SideBarCustom: View {
#State var isToggle = false
var names = ["Home", "Products", "Compare", "AR", "Search"]
var icons = ["house.fill", "printer.fill.and.paper.fill", "list.bullet.rectangle", "arkit", "magnifyingglass"]
var imgSize = 20
var body: some View {
GeometryReader { geo in
VStack {
Button(action: {
self.isToggle.toggle()
}, label: {
Image("hexagons")
.resizable()
.frame(width: 40, height: 40)
.padding(.bottom, 20)
})
if isToggle {
ZStack{
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.red)
.frame(width: 70, height: geo.size.height)
VStack(alignment: .center, spacing: 60) {
ForEach(Array(zip(names, icons)), id: \.0) { item in
Button(action: {
// NAVIIGATE TO VIEW
}, label: {
VStack {
Image(systemName: item.1)
.resizable()
.frame(width: CGFloat(imgSize), height: CGFloat(imgSize))
Text(item.0)
}
})
}
}
}
}
}
}
}
}
I don't think there's necessarily a reason to use GeometryReader here. The following is an example that has a dynamic width sidebar (although you could set it to a fixed value) that slides in and out. The main content view resizes itself automatically, since it's in an HStack:
struct ContentView : View {
#State private var sidebarShown = false
var body: some View {
HStack {
if sidebarShown {
CustomSidebar(sidebarShown: $sidebarShown)
.frame(maxHeight: .infinity)
.border(Color.red)
.transition(sidebarShown ? .move(edge: .leading) : .move(edge: .trailing) )
}
ZStack(alignment: .topLeading) {
MainContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
if !sidebarShown {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
}
}
}
}
}
struct CustomSidebar : View {
#Binding var sidebarShown : Bool
var body: some View {
VStack {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
Spacer()
Text("Hi")
Text("There")
Text("World")
Spacer()
}
}
}
struct MainContentView: View {
var body: some View {
VStack {
Text("Main content")
}
}
}
How to hide the tabBar in specific screens? I'm navigating from login to directly to tabBar. Is there any way to hide? In UIKit we're hiding by pushing and I have no idea how to do it in SwiftUI, by presenting the view not going to work.
Here is my TabBar
struct ReceiverTabBar: View {
#State private var selection: Int = 0
var body: some View {
TabView(selection: $selection){
.tabItem {
selection == 0 ? Image("")
Text("")
}
.tag(0)
ReceiverProfileView()
.tabItem {
selection == 1 ? Image("")
Text("")
}
.tag(1)
ReceiverNotificationsView()
.tabItem {
selection == 2 ? Image("")
Text("")
}
.tag(2)
ReceiverMoreView()
.tabItem {
selection == 3 ? Image("")
Text("")
}
.tag(3)
}
.accentColor(.black)
}
}
and I want hide tabBar in this view
struct MakingDonationView: View {
#Environment(\.presentationMode) var presentationMode
#State var selected = 0
var body: some View {
ScrollView(showsIndicators: false) {
Image("")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.horizontal,30)
.padding(.top,40)
.frame(height: UIScreen.main.bounds.height/5)
Text("")
.font(.custom("Poppins-SemiBold", size: 16))
.foregroundColor(Color("#252422"))
.padding(.top,20)
Text("")
.font(.custom("Poppins-SemiBold", size: 12))
.foregroundColor(Color("#5E5E5E"))
Text("")
.font(.custom("Poppins-Medium", size: 12))
.foregroundColor(Color("#A0A0A0"))
}
Spacer()
Divider()
MakingDonation(selected: $selected)
}
.padding(.all)
}
.padding(.horizontal,20)
.edgesIgnoringSafeArea(.bottom)
}
Button(action: {
}, label: {
Spacer()
Text("Confirm Donation Amount")
.font(.custom("Poppins-SemiBold", size: 13))
.foregroundColor(.black)
Spacer()
})
.frame(height:44)
.background(Color("#FFA919"))
.padding(.horizontal,20)
}
.shadow(color: .gray, radius: 1, x: 0, y: 2)
.cornerRadius(4)
}
.shadow(color: .gray, radius: 2, x: 0, y: 2)
.edgesIgnoringSafeArea(.all)
.frame(height:80)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("Making Donation", displayMode: .inline)
}
}
func goBack(){
self.presentationMode.wrappedValue.dismiss()
}
}
Try this:
// Container Screen view for handling all screen
struct ContainerView: View {
#State var tabSelection: Screens = .screen1
var body: some View {
NavigationView{
TabView(selection: $tabSelection){
// Screen 1
// Hide tab bar view only for Screen 1
NavigationLink(destination: PushedView()){
VStack{
Text("Screen 1")
Text("Tap to PushedView")
}
}
.tabItem { Text("Screen 1") }
.tag(Screens.screen1)
// Screen 2
// same view using for screen 2 for directly shows on that
PushedView()
.tabItem { Text("Screen 2") }
.tag(Screens.screen2)
}
.navigationBarTitle( self.tabSelection.title)
}
}
}
// New view for pushing
struct PushedView: View {
var body: some View {
Text("Hi! This is the new View")
.navigationBarTitle("NewView")
}
}
// Tab screens
enum Screens{
case screen1, screen2
var title: String {
switch self {
case .screen1:
return "Screen1"
case .screen2:
return "Screen2"
}
}
}