Custom Tab Bar SwiftUi [duplicate] - ios

This question already has answers here:
iOS 14 SwiftUI Keyboard lifts view automatically
(4 answers)
Closed 2 years ago.
SwiftUi am learning new. I created a tab bar, but when the keyboard is turned on, the tabbar goes up with the keyboard.
How can I fix it. Can you help me?
I want to fix the tab bar to the bottom.
Or is there something that can get the keyboard to the top of the pages?
Since I did not know where to fix it, I had to throw out all the codes.
struct ContentView: View {
#State var selected = 0
var body: some View {
VStack{
if self.selected == 0{
Color.blue
}
else if self.selected == 1{
Color.red
}
else if self.selected == 2{
Color.pink
}
else if self.selected == 3{
Color.green
}
else if self.selected == 4{
Color.yellow
}
Spacer()
ZStack(alignment: .top){
BottomBar(selected: self.$selected)
.padding()
.padding(.horizontal, 22)
.background(CurvedShape())
Button(action: {
self.selected = 4
}) {
Image(systemName:"heart.fill").renderingMode(.original).padding(18)
}.background(Color.yellow)
.clipShape(Circle())
.offset(y: -32)
.shadow(radius: 5)
}
}.background(Color("Color").edgesIgnoringSafeArea(.top))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CurvedShape : View {
var body : some View{
Path{path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: 0))
path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: 55))
path.addArc(center: CGPoint(x: UIScreen.main.bounds.width / 2, y: 55),
radius: 30, startAngle: .zero, endAngle: .init(degrees: 180), clockwise: true)
path.addLine(to: CGPoint(x: 0, y: 55))
}.fill(Color.white)
.rotationEffect(.init(degrees: 180))
}
}
struct BottomBar : View {
#Binding var selected : Int
var body : some View{
HStack{
Button(action: {
self.selected = 0
}) {
Image(systemName:"lasso")
}.foregroundColor(self.selected == 0 ? .black : .gray)
Spacer(minLength: 12)
Button(action: {
self.selected = 1
}) {
Image(systemName: "trash")
}.foregroundColor(self.selected == 1 ? .black : .gray)
Spacer().frame(width: 120)
Button(action: {
self.selected = 2
}) {
Image(systemName:"person")
}.foregroundColor(self.selected == 2 ? .black : .gray)
.offset(x: -10)
Spacer(minLength: 12)
Button(action: {
self.selected = 3
}) {
Image(systemName: "pencil")
}.foregroundColor(self.selected == 3 ? .black : .gray)
}
}
}

Set
.ignoresSafeArea(.keyboard)
To content view.

Related

Expandable Custom Segmented Picker in SwiftUI

I'm trying to create an expandable segmented picker in SwiftUI, I've done this so far :
struct CustomSegmentedPicker: View {
#Binding var preselectedIndex: Int
#State var isExpanded = false
var options: [String]
let color = Color.orange
var body: some View {
HStack {
ScrollView(.horizontal) {
HStack(spacing: 4) {
ForEach(options.indices, id:\.self) { index in
let isSelected = preselectedIndex == index
ZStack {
Rectangle()
.fill(isSelected ? color : .white)
.cornerRadius(30)
.padding(5)
.onTapGesture {
preselectedIndex = index
withAnimation(.easeInOut(duration: 0.5)) {
isExpanded.toggle()
}
}
}
.shadow(color: Color(UIColor.lightGray), radius: 2)
.overlay(
Text(options[index])
.fontWeight(isSelected ? .bold : .regular)
.foregroundColor(isSelected ? .white : .black)
)
.frame(width: 80)
}
}
}
.transition(.move(edge: .trailing))
.frame(width: isExpanded ? 80 : CGFloat(options.count) * 80 + 10, height: 50)
.background(Color(UIColor.cyan))
.cornerRadius(30)
.clipped()
Spacer()
}
}
}
Which gives this result :
Now, when it contracts, how can I keep showing the item selected and hide the others ? (for the moment, the item on the left is always shown when not expanded)
Nice job. You can add an .offset() to the contents of the ScollView, which shifts it left depending on the selection:
HStack {
ScrollView(.horizontal) {
HStack(spacing: 4) {
ForEach(options.indices, id:\.self) { index in
let isSelected = preselectedIndex == index
ZStack {
Rectangle()
.fill(isSelected ? color : .white)
.cornerRadius(30)
.padding(5)
.onTapGesture {
preselectedIndex = index
withAnimation(.easeInOut(duration: 0.5)) {
isExpanded.toggle()
}
}
}
.shadow(color: Color(UIColor.lightGray), radius: 2)
.overlay(
Text(options[index])
.fontWeight(isSelected ? .bold : .regular)
.foregroundColor(isSelected ? .white : .black)
)
.frame(width: 80)
}
}
.offset(x: isExpanded ? CGFloat(-84 * preselectedIndex) : 0) // <<< here
}
.transition(.move(edge: .trailing))
.frame(width: isExpanded ? 80 : CGFloat(options.count) * 80 + 10, height: 50)
.background(Color(UIColor.cyan))
.cornerRadius(30)
.clipped()
Spacer()
}
Here is another approach using .matchedGeometryEffect, which can handle different label widths without falling back to GeometryReader.
Based on expansionState it either draws only the selected item or all of them and .matchedGeometryEffect makes sure the animation goes smooth.
struct CustomSegmentedPicker: View {
#Binding var preselectedIndex: Int
#State var isExpanded = false
var options: [String]
let color = Color.orange
#Namespace var nspace
var body: some View {
HStack {
HStack(spacing: 8) {
if isExpanded == false { // show only selected option
optionLabel(index: preselectedIndex)
.id(preselectedIndex)
.matchedGeometryEffect(id: preselectedIndex, in: nspace, isSource: true)
} else { // show all options
ForEach(options.indices, id:\.self) { index in
optionLabel(index: index)
.id(index)
.matchedGeometryEffect(id: index, in: nspace, isSource: true)
}
}
}
.padding(5)
.background(Color(UIColor.cyan))
.cornerRadius(30)
Spacer()
}
}
func optionLabel(index: Int) -> some View {
let isSelected = preselectedIndex == index
return Text(options[index])
.fontWeight(isSelected ? .bold : .regular)
.foregroundColor(isSelected ? .white : .black)
.padding(8)
.background {
Rectangle()
.fill(isSelected ? color : .white)
.cornerRadius(30)
}
.onTapGesture {
preselectedIndex = index
withAnimation(.easeInOut(duration: 0.5)) {
isExpanded.toggle()
}
}
}
}

How to create dragable view from top in SwiftUI?

I am trying to create dragable view from top which is kind of overlay .. I am attaching something same but having bottom sheet but i am looking for Top sheet. When overlay come background should be blur.
I am not sure about how to implement hence dont have code..I tried parent child for List in SwiftUI but that is having diff. Behaviour.
Any example or suggestion is appropriated.
something like this?
struct SheetView: View {
let title: String
var body: some View {
NavigationView {
List {
ForEach(0..<30) { item in
Text("Item \(item)")
}
}
.listStyle(.plain)
.navigationTitle(title)
}
}
}
struct ContentView: View {
#State private var currentDrag: CGFloat = 0
#State private var stateDrag: CGFloat = -300
let minDrag: CGFloat = -UIScreen.main.bounds.height + 120
var body: some View {
ZStack {
VStack(spacing: 0) {
Spacer(minLength: 36)
SheetView(title: "Bottom Sheet")
.background(.primary)
.opacity(stateDrag + currentDrag > minDrag ? 0.5 : 1)
.blur(radius: stateDrag + currentDrag > minDrag ? 5 : 0)
}
VStack(spacing: 0) {
SheetView(title: "Top Sheet")
.background(.background)
.offset(x: 0, y: stateDrag + currentDrag)
Image(systemName: "line.3.horizontal").foregroundColor(.secondary)
.padding()
.frame(maxWidth: .infinity)
.background(.background)
.contentShape(Rectangle())
.offset(x: 0, y: stateDrag + currentDrag)
.gesture(DragGesture()
.onChanged { value in
withAnimation {
currentDrag = value.translation.height
}
}
.onEnded { value in
stateDrag += value.translation.height
stateDrag = max(stateDrag, minDrag)
currentDrag = .zero
})
}
}
}
}

How to animate a button getting clicked inside a forEach loop?

I am trying to animate the button when user taps on it. It offsets on the side making it look like it's pressed.
If you look at the images you should see why I want to animate it by offsetting it since I have a background behind the button which is offset and the button should match that frame when clicked.
Currently the button animates as shown in the picture when tapped but all of the button animates getting pressed and they don't return to original position after the click happens.
Button before clicked
Button after click
Below is the buttons array:
#State private var isClicked = false
let buttons: [[CalcButton]] = [
[.clear, .plusMinus, .percent, .divide],
[.seven, .eight, .nine, .multiply],
[.four, .five, .six, .minus],
[.one, .two, .three, .add],
[.zero, .doubleZero, .decimal, .equals]
]
ForEach(buttons, id: \.self) { row in
HStack(spacing: 20) {
ForEach(row, id: \.self) { item in
Button(action: {
withAnimation {
self.animation()
}
} , label: {
ZStack {
Rectangle()
.frame(width: buttonWidth(button: item), height: buttonHeight())
.foregroundColor(.backgroundColor)
.offset(x: 7.0, y: 7.0)
Rectangle()
.frame(width: buttonWidth(button: item), height: buttonHeight())
.foregroundColor(.white)
Text(item.rawValue)
.font(.custom("ChicagoFLF", size: 27))
.frame(width: buttonWidth(button: item), height: buttonHeight())
.foregroundColor(.backgroundColor)
.border(Color.backgroundColor, width: 4)
.offset(x: isClicked ? 7 : 0, y: isClicked ? 7 : 0)
}
})
}
}
.padding(.bottom, 10)
}
This is the function to toggle the isClicked state variable
func animation() {
self.isClicked.toggle()
}
You need a selection state for each button. so better to create a custom button.
Here is the demo version code.
Custom Button view
struct CustomButton: View {
var text: String
var action: () -> Void
#State private var isPressed = false
var body: some View {
Button(action: {
// Do something..
}, label: {
ZStack {
Rectangle()
.foregroundColor(.black)
.offset(x: 7.0, y: 7.0)
Rectangle()
.foregroundColor(.white)
Text(text)
.frame(width: 50, height: 50)
.foregroundColor(.black)
.border(Color.black, width: 4)
.offset(x: isPressed ? 7 : 0, y: isPressed ? 7 : 0)
}
})
.buttonStyle(PlainButtonStyle())
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged({ _ in
// Comment this line if you want stay effect after clicked
isPressed = true
})
.onEnded({ _ in
isPressed = false
// // Uncomment below line and comment above line if you want stay effect after clicked
//isPressed.toggle()
action()
})
)
.frame(width: 50, height: 50)
}
}
Usage:
struct DemoView: View {
var body: some View {
HStack(spacing: 10) {
ForEach(0..<10) { index in
CustomButton(text: index.description) {
print("Action")
}
}
}
}
}
If you want to keep your effect after clicked. Just replace this code part.
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged({ _ in
})
.onEnded({ _ in
isPressed.toggle()
action()
})
)

SwiftUI - stacking views like the notifications on the Lockscreen

I am trying to stack views of my app like the notifications on the lockscreen. (See attachment) I want them to be able to stack by clicking the button and expand on a click on the view. I have tried many things on VStack and setting the offsets of the other views but without success.
Is there maybe a standardized and easy way of doing this?
Somebody else have tried it and has a tip for me programming this?
Thanks
Notifications stacked
Notifications expanded
EDIT:
So this is some Code that I've tried:
VStack{
ToDoItemView(toDoItem: ToDoItemModel.toDo1)
.background(Color.green)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.zIndex(2.0)
ToDoItemView(toDoItem: ToDoItemModel.toDo2)
.background(Color.blue)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.zIndex(1.0)
.offset(x: 0.0, y: offset)
ToDoItemView(toDoItem: ToDoItemModel.toDo3)
.background(Color.yellow)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.offset(x: 0.0, y: offset1)
ToDoItemView(toDoItem: ToDoItemModel.toDo3)
.background(Color.gray)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.offset(x: 0.0, y: offset1)
ToDoItemView(toDoItem: ToDoItemModel.toDo3)
.background(Color.pink)
.cornerRadius(20.0)
.padding(.horizontal, 8)
.padding(.bottom, -1)
.offset(x: 0.0, y: offset1)
}
Button(action: {
if(!stacked){
stacked.toggle()
withAnimation(.easeOut(duration: 0.5)) { self.offset = -100.0 }
withAnimation(.easeOut(duration: 0.5)) { self.offset1 = -202.0 }
}
else {
stacked.toggle()
withAnimation(.easeOut(duration: 0.5)) { self.offset = 0 }
withAnimation(.easeOut(duration: 0.5)) { self.offset1 = 0 }
}
}, label: {
Text("Button")
})
So the first problem is that view 4 and 5 are not stacked on z behind view 3.
The second problem is that the button is not getting under the 5th view. (the frame/VStack of the views is still the old size)
Views stacked vertical
Views stacked on z
Another way using ZStack if your interested.
Note-:
I have declared variables globally in view, you can create your own model if you wish.
struct ContentViewsss: View {
#State var isExpanded = false
var color :[Color] = [Color.green,Color.gray,Color.blue,Color.red]
var opacitys :[Double] = [1.0,0.7,0.5,0.3]
var height:CGFloat
var width:CGFloat
var offsetUp:CGFloat
var offsetDown:CGFloat
init(height:CGFloat = 100.0,width:CGFloat = 400.0,offsetUp:CGFloat = 10.0,offsetDown:CGFloat = 10.0) {
self.height = height
self.width = width
self.offsetUp = offsetUp
self.offsetDown = offsetDown
}
var body: some View {
ZStack{
ForEach((0..<color.count).reversed(), id: \.self){ index in
Text("\(index)")
.frame(width: width , height:height)
.padding(.horizontal,isExpanded ? 0.0 : CGFloat(-index * 4))
.background(color[index])
.cornerRadius(height/2)
.opacity(isExpanded ? 1.0 : opacitys[index])
.offset(x: 0.0, y: isExpanded ? (height + (CGFloat(index) * (height + offsetDown))) : (offsetUp * CGFloat(index)))
}
Button(action:{
withAnimation {
if isExpanded{
isExpanded = false
}else{
isExpanded = true
}
}
} , label: {
Text(isExpanded ? "Stack":"Expand")
}).offset(x: 0.0, y: isExpanded ? (height + (CGFloat(color.count) * (height + offsetDown))) : offsetDown * CGFloat(color.count * 3))
}.padding()
Spacer().frame(height: 550)
}
}
I find it a little easier to get exact positioning using GeometryReader than using ZStack and trying to fiddle with offsets/positions.
This code has some numbers hard-coded in that you might want to make more dynamic, but it does function. It uses a lot of ideas you were already doing (zIndex, offset, etc):
struct ToDoItemModel {
var id = UUID()
var color : Color
}
struct ToDoItemView : View {
var body: some View {
RoundedRectangle(cornerRadius: 20).fill(Color.clear)
.frame(height: 100)
}
}
struct ContentView : View {
#State private var stacked = true
var todos : [ToDoItemModel] = [.init(color: .green),.init(color: .pink),.init(color: .orange),.init(color: .blue)]
func offsetForIndex(_ index : Int) -> CGFloat {
CGFloat((todos.count - index - 1) * (stacked ? 4 : 104) )
}
func horizontalPaddingForIndex(_ index : Int) -> CGFloat {
stacked ? CGFloat(todos.count - index) * 4 : 4
}
var body: some View {
VStack {
GeometryReader { reader in
ForEach(Array(todos.reversed().enumerated()), id: \.1.id) { (index, item) in
ToDoItemView()
.background(item.color)
.cornerRadius(20)
.padding(.horizontal, horizontalPaddingForIndex(index))
.offset(x: 0, y: offsetForIndex(index))
.zIndex(Double(index))
}
}
.frame(height:
stacked ? CGFloat(100 + todos.count * 4) : CGFloat(todos.count * 104)
)
.border(Color.red)
Button(action: {
withAnimation {
stacked.toggle()
}
}) {
Text("Unstack")
}
Spacer()
}
}
}

Can I round the corners of an HStack's background without clipping the content inside of it?

I'm developing an app in which I use a tab bar br using HStack{} with Button functions inside of it as shown here: https://i.stack.imgur.com/18Amd.png and I'm trying to round the corners of the bar.
I've tried using .clipShape(RoundedRectangle(cornerRadius:20)) but it will cut out any content outside of the bounds of the background: https://i.stack.imgur.com/zVZb6.png.
Is there a way to round the corners of the HStack background without clipping out the buttons inside of it?
I'm using Xcode 12.0 with SwiftUI.
The code that I'm using is here:
struct TabBar: View {
#Binding var selectedTab : Int
//To assign item color when selected
#State var icon = "unSelect"
var body: some View {
//Icon Design
HStack(alignment: .center) {
Group {
//Home Icon
Button(action: {
self.selectedTab = 0
}) {
HStack(spacing: 5) {
Image("Home").resizable()
.frame(width: 40, height: 40)
}
.padding(5)
}
.foregroundColor(self.selectedTab == 0 ? Color("Select") : Color("unSelect"))
//Chart Icon
Button(action: {
self.selectedTab = 1
}) {
HStack(spacing: 5) {
Image("Chart").resizable()
.frame(width: 40, height: 40)
}
.padding(5)
}
.foregroundColor(self.selectedTab == 1 ? Color("Select") : Color("unSelect"))
//Add Icon
Button(action: {
self.selectedTab = 2
}) {
HStack(spacing: 5) {
Image("Add")
.resizable()
.frame(width: 83.52, height: 83.52, alignment: .top)
.overlay(Circle().stroke(Color("Tab"), lineWidth: 8))
}
.offset(y: -17)
.edgesIgnoringSafeArea(.all)
}
//Log Icon
Button(action: {
self.selectedTab = 3
}) {
HStack(spacing: 54) {
Image("Log").resizable()
.frame(width: 50, height: 50)
}
.padding(4)
}
.foregroundColor(self.selectedTab == 3 ? Color("Select") : Color("unSelect"))
//Settings Icon
Button(action: {
self.selectedTab = 4
}) {
HStack(spacing: 5) {
Image("Settings").resizable()
.frame(width: 40, height: 40)
}
.padding(5)
}
.foregroundColor(self.selectedTab == 4 ? Color("Select") : Color("unSelect"))
}
}
.padding(.vertical, -10)
.padding(.horizontal, 30)
.background(Color("Tab"))
.padding(.top)
.padding(.top)
.clipShape(RoundedRectangle(cornerRadius:20))
}
Try applying the .clipShape to the Color() inside of .background():
.background(Color("Tab").clipShape(RoundedRectangle(cornerRadius:20)))
and remove it from end.
Try adding padding before .clipShape(RoundedRectangle(cornerRadius:20)):
.padding(.top)

Resources