How can I read Location of tap on a View in SwiftUI? - swiftui

Here I have this Rectangle, how could I read the x location of tap on Rectangle?
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 50, alignment: .center)
.padding()
.onTapGesture {
print("location of tap on Rec is = ???")
}
}
}
update:
Rectangle()
.fill(Color.blue)
.frame(height: 50, alignment: .center)
.padding()
.onLongPressGesture(minimumDuration: 0.01, maximumDistance: 0) {
print("onLongPressGesture!")
}

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

Limit the number of dots in Page Tab View Swiftui

I am trying to use the PageTabview option to allow a user to move through a series of pages whose data is coming from a JSON file. I want to be able to limit the number of visible dots to 5 or 6 even if there are many values in the field. What I don't want is to have 25 dots if there are twenty-five values in the field. How would I do that? I want to be able to show indicator like an arrow that tells the user there is more to come...Thank you.
My code is below:
struct TopicsExperienceCards: View {
#Binding var closeExperience: Bool
let etype: EItype
var body: some View {
//start of content of zstack layout
ZStack {
VStack(spacing: 20) {
HStack{
Rectangle()
.fill(Color.white)
.frame(width: 300, height: 1, alignment: .center)
Spacer()
Button(action: {
closeExperience = false })
{
Image(systemName:"xmark")
.foregroundColor(Color(etype.accentcolor))
.padding()
}
} //HSTACK
TabView {
ForEach(etype.experience,id: \.self) { item in
// Display the content of a card //
VStack (alignment:.center, spacing:0){
Text(item)
.padding()
.frame(width:300, height:300, alignment:.center)
Divider()
Spacer()
Text("Room for an image")
Spacer()
Spacer()
}//VSTACK
//End of display of content of the card //
} //: FOREACH
} //: TABVIEW
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.onAppear {
setupAppearance() }
} //VSTACK
} //: ZStack
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 16)
.strokeBorder()
.foregroundColor(Color(etype.accentcolor)))
.cornerRadius(16.0)
.padding()
}
}
struct TopicsExperienceCards_Previews: PreviewProvider {
static let etypes: [EItype] = Bundle.main.decode("eibasestructure.json")
static var previews: some View {
TopicsExperienceCards(closeExperience:.constant(true),etype:etypes[1])
}
}
enter image description here
System dots view is limited to around 10 dots, maybe depending on the device. You can't change this value.
Instead of that you can hide system one, and create your own view with dots. As an example you can follow this article, so at the end you'll have something like this:
#State var currentIndex = 0
var body: some View {
//start of content of zstack layout
ZStack {
printUI(currentIndex)
VStack(spacing: 20) {
HStack{
Rectangle()
.fill(Color.white)
.frame(width: 300, height: 1, alignment: .center)
Spacer()
Button(action: {
closeExperience = false })
{
Image(systemName:"xmark")
.foregroundColor(Color.red)
.padding()
}
} //HSTACK
TabView(selection: $currentIndex.animation()) {
ForEach(etype.experience.indices,id: \.self) { i in
let item = etype.experience[i]
// Display the content of a card //
VStack (alignment:.center, spacing:0){
Text(item)
.padding()
.frame(width:300, height:300, alignment:.center)
Divider()
Spacer()
Text("Room for an image")
Spacer()
Spacer()
}//VSTACK
//End of display of content of the card //
} //: FOREACH
} //: TABVIEW
.tabViewStyle(PageTabViewStyle())
.onAppear {
setupAppearance()
}
Fancy3DotsIndexView(numberOfPages: etype.experience.count, currentIndex: currentIndex)
.padding()
.background(Color.green)
.clipShape(Capsule())
} //VSTACK
} //: ZStack
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 16)
.strokeBorder()
.foregroundColor(Color.red)
)
.cornerRadius(16.0)
.padding()
}
Result:

Variable Rectangle() dimension based on cell size to draw a timeline

I am trying to build a List that I want to look like a timeline.
Each cell will represent a milestone.
Down the left hand side of the table, I want the cells to be 'connected', by a line (the timeline).
I have tried various things to get it to display as I want but I have settled with basic geometric shapes , i.e Circle() and Rectangle().
This is sample code to highlight the problem:
import SwiftUI
struct ContentView: View {
var body: some View {
let roles: [String] = ["CEO", "CFO", "Managing Director and Chairman of the supervisory board", "Systems Analyst", "Supply Chain Expert"]
NavigationView{
VStack{
List {
ForEach(0..<5) { toto in
NavigationLink(
destination: dummyView()
) {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .center, spacing: 0){
Rectangle()
.frame(width: 1, height: 30, alignment: .center)
Circle()
.frame(width: 10, height: 10)
Rectangle()
.frame(width: 1, height: 20, alignment: .center)
Circle()
.frame(width: 30, height: 30)
.overlay(
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.system(size: 30, weight: .light , design: .rounded))
.frame(width: 30, height: 30)
)
//THIS IS THE RECTANGLE OBJECT FOR WHICH I WANT THE HEIGHT TO BE VARIABLE
Rectangle()
.frame(width: 1, height: 40, alignment: .center)
.foregroundColor(.green)
}
.frame(width: 32, height: 80, alignment: .center)
.foregroundColor(.green)
VStack(alignment: .leading, spacing: 0, content: {
Text("Dummy operation text that will be in the top of the cell")
.font(.subheadline)
.multilineTextAlignment(.leading)
.lineLimit(1)
Label {
Text("March 6, 2021")
.font(.caption2)
} icon: {
Image(systemName: "calendar.badge.clock")
}
HStack{
HStack{
Image(systemName: "flag.fill")
Text("In Progress")
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.blue)
.background(Color.white)
.cornerRadius(5, antialiased: true)
HStack{
Image(systemName: "person.fill")
Text(roles[toto])
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.green)
.background(Color.white)
.cornerRadius(5, antialiased: true)
HStack{
Image(systemName: "deskclock")
Text("in 2 Months")
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.red
)
.background(
Color.white
)
.cornerRadius(5, antialiased: true)
}
})
}.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct dummyView: View {
var body: some View {
Text("Hello, World!")
}
}
struct dummyView_Previews: PreviewProvider {
static var previews: some View {
dummyView()
}
}
but as you can see in the enclosed picture, there are unwanted gaps
So other content in the cell is making the height of the entire cell 'unpredictable' and break the line.
Is there a way to determine the height of the cell and extend the dimensions of the Rectangle, so that it extends to the full height of the cell?
Is there a better approach you recommend for trying to build such a timeline ?
PS: I have tried playing around with .frame and .infinity but that does work.
Many thanks.
Why not just draw the line based on the size of the row. See Creating custom paths with SwiftUI. Remember, everything is a view.
First, you need to decompose what you are doing into subviews. You have too many moving parts in one view to get it correct. Also, I would avoid setting specific padding amounts as that will mess you up when you change devices. You want a simple, smart view that is generic enough to handle different devices.
I would have a row view that has a geometry reader so it knows its own height. You could then draw the line so that it spanned the full height of the row, regardless of the height. Something along the lines of this:
struct ListRow: View {
var body: some View {
GeometryReader { geometry in
ZStack {
HStack {
Spacer()
Text("Hello, World!")
Spacer()
}
VerticalLine(geometry: geometry)
}
}
}
}
and
struct VerticalLine: View {
let geometry: GeometryProxy
var body: some View {
Path { path in
path.move(to: CGPoint(x: 20, y: -30))
path.addLine(to: CGPoint(x: 20, y: geometry.size.height+30))
}
.stroke(Color.green, lineWidth: 4)
}
}

SwiftUI not centering when in ZStack

I am trying to put together a view that consists of a top header view, a bottom content view, and a view that sits on top centered on the line splitting the two views. I figured out I need an alignment guide within a ZStack to position the middle view but I am having problems getting the items in the lower content view centered without a gap.
This code:
extension VerticalAlignment {
struct ButtonMid: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context[.bottom]
}
}
static let buttonMid = VerticalAlignment(ButtonMid.self)
}
struct ContentView: View {
var body: some View {
VStack {
ZStack(alignment: Alignment(horizontal: .center, vertical: .buttonMid)) {
HeaderView()
.frame(maxWidth: .infinity, minHeight: 200, idealHeight: 200, maxHeight: 200, alignment: .topLeading)
// BodyView()
// .alignmentGuide(.buttonMid, computeValue: { dimension in
// return dimension[VerticalAlignment.top]
// })
Color.red
.frame(width: 380, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.alignmentGuide(.buttonMid, computeValue: { dimension in
return dimension[VerticalAlignment.center]
})
}
BodyView()
}
.edgesIgnoringSafeArea(.top)
}
}
struct HeaderView: View {
var body: some View {
Color.green
}
}
struct BodyView: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
BodyContent()
Spacer()
}
Spacer()
}
.background(Color.blue)
}
}
struct BodyContent: View {
var body: some View {
VStack {
Text("Line 1")
Text("Line 2")
Text("Line 3")
}
}
}
give you this:
which centers the lower content they way I want it however it leaves a gap between the upper and lower views. If I uncomment the BodyView code in the ZStack and comment it out in the VStack like so:
struct ContentView: View {
var body: some View {
VStack {
ZStack(alignment: Alignment(horizontal: .center, vertical: .buttonMid)) {
HeaderView()
.frame(maxWidth: .infinity, minHeight: 200, idealHeight: 200, maxHeight: 200, alignment: .topLeading)
BodyView()
.alignmentGuide(.buttonMid, computeValue: { dimension in
return dimension[VerticalAlignment.top]
})
Color.red
.frame(width: 380, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.alignmentGuide(.buttonMid, computeValue: { dimension in
return dimension[VerticalAlignment.center]
})
}
// BodyView()
}
.edgesIgnoringSafeArea(.top)
}
}
gives you:
which leaves the content uncentered. How can I keep it centered? I tried putting it in a GeometryReader and that had the same results.
You don't need a custom VerticalAlignment. Instead you can put the middle view as an overlay and align it to the top border of the bottom view:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
HeaderView()
.frame(height: 200)
BodyView()
.overlay(
Color.red
.frame(width: 380, height: 50)
.alignmentGuide(.top) { $0[VerticalAlignment.center] },
alignment: .top
)
}
.edgesIgnoringSafeArea(.top)
}
}

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: