Why the text appear at loading with animation? - swiftui

I want to hide some text behind the NavigationBarTitle and show it when user press a button, it's working fine. The only problem is with the animation. At loading we see the text moving behind the NavigationBarTitle and that's not what i want.
How can i fix this?
I tried with offset and position but it's not working...
Code :
import SwiftUI
struct TestZStackNavigationView: View {
#State var isShowed = false
let screenSize: CGRect = UIScreen.main.bounds
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0){
Text("Im the hidden text")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: 40, alignment: .center)
.background(Color.red)
// .offset(y: self.isShowed ? -315 : -355)
.position(x: screenSize.width / 2, y: self.isShowed ? 20 : -40)
.animation(.easeIn(duration: 0.5))
Button(action: {
self.isShowed.toggle()
}) {
Text("click me")
}
.navigationBarTitle(Text("Navigation Bar Title"), displayMode:.inline)
}
}
}
}
struct TestZStackNavigationView_Previews: PreviewProvider {
static var previews: some View {
TestZStackNavigationView()
}
}

There are two possibilities
make animation per-state (and no other changes)
.animation(.easeIn(duration: 0.5), value: isShowed)
replace implicit animation as modifier and add explicit animation in action
.position(x: screenSize.width / 2, y: self.isShowed ? 20 : -40)
// .animation(.easeIn(duration: 0.5)) // remove from here
Button(action: {
withAnimation(Animation.easeIn(duration: 0.5)) { // << add this
self.isShowed.toggle()
}
}) {
Text("click me")
}

I want to use it now with an ObservedObject but i got that same behavior as first code. Why?
struct TestZStackNavigationView: View {
// #State var isShowed = false
let screenSize: CGRect = UIScreen.main.bounds
#ObservedObject var online = NetStatus()
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0){
Text("Im the hidden text")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: 40, alignment: .center)
.background(Color.red)
.position(x: screenSize.width / 2, y: online.connected ? -40 : 20)
.animation(.easeIn(duration: 0.5), value: online.connected)
.navigationBarTitle(Text("Navigation Bar Title"), displayMode:.inline)
}
}
}
}

Related

RoundedRectangle background colour does not crop and TextEditor transparent background

I have a messaging interface. When user types in to the texteditor it will be append to messagesDBArray and will be displayed in textview. Once new messages are there it should scroll to the bottom. But I'm having issues.
Errors: no errors
RoundedRectangle background colour green overflows from corners (does not crop as rounded)
TextEditor (not textview) is not transparent (so it can have rounded rectangle color underneath)
proxy.scrollTo(id, anchor: .bottom) does not scrolls to the last message.
import SwiftUI
final class ViewModel: ObservableObject {
#Published var messagesDBArray : [SingleMessageBubbleModel] = []
}
struct SingleMessageBubbleModel: Identifiable {
let id = UUID()
var text: String
var received: Bool
var timeStamp: Date
}
var messagesDBArray : [SingleMessageBubbleModel] = []
struct ContentView: View {
#ObservedObject private var messageArrayObservedObject = ViewModel()
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
VStack (alignment: .center) {
ZStack (alignment: .center) {
HStack () {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.stroke(Color.brown, lineWidth: 1)
.frame(width: 300, alignment: Alignment.top )
.padding([.bottom], 5)
.clipped()
.background(Color.green)
}
HStack () {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(
messageArrayObservedObject.messagesDBArray,
id: \.id
) {
message in MessageBubble(message: message)
}
}
}
.frame(alignment: .center)
.background(Color.clear)
.padding (.vertical, 5)
.padding (.horizontal,5)
.padding(.bottom, 5)
.onChange(
of: messageArrayObservedObject.messagesDBArray.count
) { id in
// When the lastMessageId changes, scroll to the bottom of the conversation
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .center)
}
.frame(width: 295, alignment: Alignment.center )
}
HStack () {
VStack {
ZStack (alignment: .center) {
HStack () {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.stroke(Color.brown , lineWidth: 1)
.frame(width: 295, alignment: Alignment.top )
.padding([.bottom], 5)
.clipped()
.background(Color.green)
// .background(Color("#E5F2E4"))
}
HStack () {
TextEditor (text: $textTyped)
.frame(height: 200, alignment: .leading)
.padding(.horizontal, 10)
.background(.clear)
}
}
.frame(width: 290, alignment: Alignment.top )
.padding(.top, 5)
}
}
}
}
struct MessageBubble: View {
var message: SingleMessageBubbleModel
#State private var showTime = false
var body: some View {
VStack(alignment: message.received ? .leading : .trailing) {
HStack {
Text(message.text)
.padding()
.background(message.received ? Color.gray : Color.blue)
.cornerRadius(30)
}
.frame(maxWidth: 300, alignment: message.received ? .leading : .trailing)
.onTapGesture {
withAnimation {
showTime.toggle()
}
}
}
.frame(maxWidth: .infinity, alignment: message.received ? .leading : .trailing)
.padding(message.received ? .leading : .trailing)
.padding(.horizontal, 4)
}
}
for the first error you should use that code instead of your code where you make a background with RoundRectangle the same to your base rectangle and make the fill of that the color you want which is green
RoundedRectangle(cornerRadius: 25, style: .continuous)
.stroke(Color.brown, lineWidth: 1).background(RoundedRectangle(cornerRadius: 25).fill(Color.green))
.frame(width: 300, alignment: Alignment.top )
.padding([.bottom], 5)
.clipped()
the second issue in your ContentView you should init your UITextView background color to clear and after that make your textEditor Color clear using that code
init() {
UITextView.appearance().backgroundColor = .clear
}
and make your textEditor background clear
TextEditor (text: $textTyped)
.frame(height: 200, alignment: .leading)
.padding(.horizontal, 10)
.background(Color.clear)
and the third issue is that i think you are using the array count but you should use the id of each message so when if we suppose that the last message-id is 728398 in your onChange
onChange(of: messageArrayObservedObject.messagesDBArray.count) { id in
// When the lastMessageId changes, scroll to the bottom of the conversation
withAnimation {
print("ididididid\(id)")
proxy.scrollTo(messageArrayObservedObject.messagesDBArray.last, anchor: .bottom)
}
}
your are using the ( messageArrayObservedObject.messagesDBArray.count )counts of messages like 5 message so you are scrolling to 5 not to the id of message which is 728398

At SwiftUI how do you assign different actions if buttons are created in a loop

How can you assign different actions if you are creating buttons in a loop when using SwiftUI.
I was using sender tag while using UIView.
At following code, every buttons calls the same function as usual.
How can I make them call different action in case we are using loops to create buttons.
var buttonNames = ["OK", "NOPE"]
var body: some View {
let width = UIScreen.main.bounds.width / CGFloat(buttonNames.count)
ScrollView (.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(0..<buttonNames.count, id: \.self) { index in
Button(buttonNames[index]) {
buttonTouched()
}.frame(width: width)
.background(Color.gray)
}
}
.frame(width: UIScreen.main.bounds.width, height: 45, alignment: .center)
.foregroundColor(.white)
.background(Color.gray)
}
.position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height - 45)
}
After the comment I did it like this and it makes the trick.
var buttonNames = ["OK", "NOPE"]
#State var touchedButton = 0
func buttonTouched(){
print("Button tapped! \(touchedButton)")
}
var body: some View {
let width = UIScreen.main.bounds.width / CGFloat(buttonNames.count)
ScrollView (.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(0..<buttonNames.count, id: \.self) {
index in
Button(action:{
touchedButton = index
buttonTouched()
}
){
Text(buttonNames[index])
}
.frame(width: width)
}
}
.frame(width: UIScreen.main.bounds.width, height: 45, alignment: .center)
.foregroundColor(.white)
.background(Color.gray)
} .position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height - 45)
}

SwiftUI Custom Tab Bar icons not changing the tab. Area is above it

I am currently having trouble with my Custom Tab Bar there is a gray area above it (Tab View) that controls each tab but I need that to go under my custom tab bar but functionality of the TabView still be in effect and be used with the icons. You can hide the Tab bar with UITabBar.apperance() which gets rid of the gray area but no longer has any functions.. but I need that gray area to go under the tabs. If that makes sense?
Home.swift
import SwiftUI
struct Home: View {
//Hiding Tab Bar..
init() {
UITabBar.appearance().isHidden = false
}
var body: some View {
VStack(spacing: 0){
//Tab View...
TabView{
Color.blue
.tag("house.circle")
Color.green
.tag("pencil")
Color.pink
.tag("magnifyingglass")
Color.red
.tag("bell")
Color.yellow
.tag("cart")
}
//Custom Tab Bar...
CustomTabBar()
}
.ignoresSafeArea()
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
//Extending View To Get Screen Frame...
extension View {
func getRect()->CGRect {
return UIScreen.main.bounds
}
}
CustomTabBar.swift
import SwiftUI
struct CustomTabBar: View {
var body: some View {
HStack(spacing: 0){
// Tab Bar Button...
TabBarButton(systemName: "house.circle")
.background(Color.blue)
TabBarButton(systemName: "pencil")
.background(Color.green)
Button(action: {}, label: {
Image(systemName: "magnifyingglass")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:24, height:24)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
//Shadows
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: -5, y: -5)
})
.tag("magnifyingglass")
TabBarButton(systemName: "bell")
.background(Color.red)
TabBarButton(systemName: "cart")
.background(Color.yellow)
}
.padding(.top)
//Decreasing the extra padding added...
.padding(.vertical, -0)
.padding(.bottom,getSafeArea().bottom == 0 ? 15 : getSafeArea().bottom)
.background(Color.white)
}
}
struct CustomTabBar_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
}
}
}
//extending view to get safe area...
extension View {
func getSafeArea()-> UIEdgeInsets {
return UIApplication.shared.windows.first?.safeAreaInsets ?? UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
struct TabBarButton: View {
var systemName: String
var body: some View{
Button(action: {
}, label: {
VStack(spacing: 8){
Image(systemName)
.resizable()
//Since its asset image...
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:28, height: 28)
}
.frame(maxWidth: .infinity)
})
}
}
EDIT: SECOND IMAGE I am hiding the tab bar setting it to true instead of false.
//Hiding Tab Bar..
init() {
UITabBar.appearance().isHidden = true
}
you could try this to "cover" the original TabView bar:
In Home replace VStack with ZStack.
and
struct CustomTabBar: View {
var body: some View {
VStack (alignment: .leading) {
Spacer()
HStack(spacing: 0) {
TabBarButton(systemName: "house.circle").background(Color.blue)
TabBarButton(systemName: "pencil").background(Color.green)
Button(action: {}, label: {
Image(systemName: "magnifyingglass")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:24, height:24)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
//Shadows
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: -5, y: -5)
})
.tag("magnifyingglass")
TabBarButton(systemName: "bell").background(Color.red)
TabBarButton(systemName: "cart").background(Color.yellow)
}
}
.padding(.bottom, getSafeArea().bottom == 0 ? 15 : getSafeArea().bottom)
.background(Color.white)
}
}
you will then need to implement the action of each of your CustomTabBar buttons.
EDIT1:
ok, as I mentioned you need to implement the actions for your buttons.
There are many ways to do this, this is just one approach:
struct CustomTabBar: View {
#Binding var tagSelect: String
var body: some View {
VStack (alignment: .leading) {
Spacer()
HStack(spacing: 0) {
TabBarButton(tagSelect: $tagSelect, systemName: "house.circle").background(Color.blue)
TabBarButton(tagSelect: $tagSelect, systemName: "pencil").background(Color.green)
Button(action: {}, label: {
Image(systemName: "magnifyingglass")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:24, height:24)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
//Shadows
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: -5, y: -5)
})
.tag("magnifyingglass")
TabBarButton(tagSelect: $tagSelect, systemName: "bell").background(Color.red)
TabBarButton(tagSelect: $tagSelect, systemName: "cart").background(Color.yellow)
}
}
.padding(.bottom,getSafeArea().bottom == 0 ? 15 : getSafeArea().bottom)
// no background or use opacity, like this
.background(Color.white.opacity(0.01)) // <-- important
}
}
extension View {
func getSafeArea()-> UIEdgeInsets {
return UIApplication.shared.windows.first?.safeAreaInsets ?? UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
struct TabBarButton: View {
#Binding var tagSelect: String
var systemName: String
var body: some View{
Button(action: {tagSelect = systemName }, label: {
VStack(spacing: 8){
Image(systemName)
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:28, height: 28)
}
.frame(maxWidth: .infinity)
})
}
}
struct Home: View {
#State var tagSelect = "house.circle"
init() {
UITabBar.appearance().isHidden = false
}
var body: some View {
ZStack {
TabView (selection: $tagSelect) {
Color.blue.tag("house.circle")
Color.green.tag("pencil")
Color.pink.tag("magnifyingglass")
Color.red.tag("bell")
Color.yellow.tag("cart")
}
CustomTabBar(tagSelect: $tagSelect)
}
.ignoresSafeArea()
}
}
extension View {
func getRect()->CGRect {
return UIScreen.main.bounds
}
}

SwiftUI: Show specific views with an animation delay

I'm trying to achieve the following animation: When I tap on a rectangle the rectangle should be expanded to the full width with an close button in the corner and below this rectangle a ScrollView should appear. This works so far without any problems. Now I would like to display the ScrollView a little bit later then the expanded rectangle. So when I tap on the rectangle: First the expanded rectangle with the close button should appear and 3 seconds later the ScrollView.
struct Playground: View {
#Namespace var namespace
#State var show = false
private let gridItems = [GridItem(.flexible())]
var body: some View {
if show {
VStack{
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)){
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: show)
.frame(height: 300)
.frame(maxWidth: .infinity)
Image(systemName: "xmark")
.font(.system(size: 25))
.foregroundColor(.white)
.background(Color.red)
.padding(20)
.onTapGesture {
withAnimation(.spring()){
self.show = false
}
}
}
// SHOW THIS SCROLLVIEW 3 SECONDS LATER
ScrollView{
LazyVGrid(columns: gridItems){
ForEach(0..<10){ cell in
Text("\(cell)")
}
}
}
.animation(Animation.spring().delay(3)) // doesn't work!
}
} else {
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: !show)
.frame(width: 100, height: 100)
.onTapGesture {
withAnimation(.spring()){
self.show = true
}
}
}
}
}
We need to make separated animation (and related state) for ScrollView in this scenario.
Here is possible approach. Tested with Xcode 12.1 / iOS 14.1
struct Playground: View {
#Namespace var namespace
#State var show = false
private let gridItems = [GridItem(.flexible())]
#State private var showItems = false
var body: some View {
if show {
VStack{
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)){
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: show)
.frame(height: 300)
.frame(maxWidth: .infinity)
Image(systemName: "xmark")
.font(.system(size: 25))
.foregroundColor(.white)
.background(Color.red)
.padding(20)
.onTapGesture {
withAnimation(.spring()){
self.show = false
}
}
}
VStack {
if showItems {
ScrollView{
LazyVGrid(columns: gridItems){
ForEach(0..<10){ cell in
Text("\(cell)")
}
}
}
} else {
Spacer()
}
}
.onAppear { showItems = true }
.onDisappear { showItems = false }
.animation(Animation.spring().delay(3), value: showItems)
}
} else {
Rectangle()
.matchedGeometryEffect(id: "A", in: namespace, isSource: !show)
.frame(width: 100, height: 100)
.onTapGesture {
withAnimation(.spring()){
self.show = true
}
}
}
}
}

swiftUI Button with width:0 nonetheless active

I set the width of a SwiftUI Button to 0 to "deactivate" it.
If the with of the button is set to 0, the button disappears as expected, but clicking in the left edge of the yellow Stack activates the Button.
Why does this happen?
How can I avoid it?
struct ContentView: View {
#State var zeroWidth = false
var body: some View {
VStack{
ButtonLine( leftButtons: [ButtonAttr( label: "LB1",
action: {print("LB1")},
iconSystemName : "person"
)],
zeroWidth: zeroWidth
)
Button("Toggle width \(zeroWidth ? "On" : "Off" ) "){ self.zeroWidth.toggle() }
}
}
}
struct ButtonLine: View {
let leftButtons : [ButtonAttr]
let zeroWidth : Bool
var body: some View {
HStack(spacing: 0) {
ForEach(leftButtons.indices, id: \.self)
{ i in
HStack(spacing: 0.0)
{
Button(action: { self.leftButtons[i].action() }) {
ButtonLabel( singleline: false,
buttonAttr: self.leftButtons[i]
)
.padding(0)
//.background(Color.green) // not visible
}
.buttonStyle(BorderlessButtonStyle())
.frame( width: self.zeroWidth ? 0 : 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)
}
// .background(Color.blue) // not visible
}
// .background(Color.blue) // not visible
Spacer()
Text("CONTENT")
.background(Color.green)
.onTapGesture {
print("Content tapped")
}
Spacer()
}
.background(Color.yellow)
.onTapGesture {
print("HS tapped")
}
}
}
struct ButtonLabel: View {
var singleline : Bool
var buttonAttr : ButtonAttr
var body: some View {
VStack (spacing: 0.0) {
Image(systemName: buttonAttr.iconSystemName).frame(height: singleline ? 0 : 20).clipped()
.padding(0)
.background(Color.blue)
Text(buttonAttr.label)
.padding(0)
.background(Color.blue)
}
.padding(0)
.background(Color.red)
}
}
struct ButtonAttr
{ let label : String
let action: ()-> Void
let iconSystemName : String
}
Instead of tricky "deactivate", just use real remove, like below
HStack(spacing: 0.0)
{
if !self.zeroWidth {
Button(action: { self.leftButtons[i].action() }) {
ButtonLabel( singleline: false,
buttonAttr: self.leftButtons[i]
)
.padding(0)
//.background(Color.green) // not visible
}
.buttonStyle(BorderlessButtonStyle())
.frame(width: 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)
}
}.frame(height: 50) // to keep height persistent
there is very simple explanation.
try next snippet
struct ContentView: View {
var body: some View {
Text("Hello").padding().border(Color.yellow).fixedSize().frame(width: 0)
}
}
Why?
.frame(..)
is defined as a function of View, which return another View, as any kind of View modifier. The resulting View has .zero sized frame, as expected.
It is really true? Let's check it!
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle()
.fill(Color.orange)
.frame(width: 100, height: 100)
Text("Hello")
.padding()
.border(Color.black)
.fixedSize()
.frame(width: 0, height: 0)
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.blendMode(.exclusion)
}
}
}
Just add .clipped modifier to your Text View
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle()
.fill(Color.orange)
.frame(width: 100, height: 100)
Text("Hello")
.padding()
.border(Color.black)
.fixedSize()
.frame(width: 0, height: 0)
.clipped()
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.blendMode(.exclusion)
}
}
}
and the Text "disappears" ...
It disappears from the screen, but not from View hierarchy!. Change the code again
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle()
.fill(Color.orange)
.frame(width: 100, height: 100)
Text("Hello")
.padding()
.border(Color.black)
.fixedSize().onTapGesture {
print("tap")
}
.frame(width: 0, height: 0)
.clipped()
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.blendMode(.exclusion)
}
}
}
and you see, that there is still some "invisible" area sensitive on tap gesture
You can disable you Button by adding a .disabled(self.zeroWidth)
Button(action: { self.leftButtons[i].action() }) {
ButtonLabel( singleline: false,
buttonAttr: self.leftButtons[i]
)
.padding(0)
//.background(Color.green) // not visible
}
.disabled(self.zeroWidth)
.buttonStyle(BorderlessButtonStyle())
.frame( width: self.zeroWidth ? 0 : 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)
You can debug the view hierarchy by clicking that icon in xcode: