I tried to do a resizable view with a gesture.
The problem is that when I move my gesture object left or right the app will be freeze with 100% processor usage. It is the loop between two Text views.
What did I do wrong, and how do I make this correct?
struct TestView2 : View {
#State private var width : CGFloat = 400.0
var body : some View {
VStack {
ZStack(alignment: .bottomTrailing) {
Text("\(width)")
.frame(width: width)
Text(":")
.frame(width: 10, height: 30)
.background(.bar)
.gesture(
DragGesture()
.onChanged { value in
width = max(100, width + value.translation.width)
print(width)
}
)
}
.frame(width: width, height: 30, alignment: .topLeading)
.border(.gray, width: 1)
.background(.green)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
View:
Debugger:
For me, the solution was to explicitly set the gesture's coordinateSpace to .global:
DragGesture(coordinateSpace: .global)
.onChanged { ... }
Related
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
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)
}
I'm trying to increase the height of the shape by using the "dragger" (rounded grey rectangle) element. I use the DragGesture helper provided by SwiftUI to get the current position of the user finger. Unfortunately, during the drag event content is jumping for some reason.
Could you please help me to find the root cause of the problem?
This is how it looks during the drag event
If I remove Spacer() everything is okay but not what I wanted
This is my code snippets
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
CustomDraggableComponent()
Spacer() // If comment this line the result will be as on the bottom GIF example
}
}
import SwiftUI
let MIN_HEIGHT = 50
struct CustomDraggableComponent: View {
#State var height: CGFloat = MIN_HEIGHT
var body: some View {
VStack {
Rectangle()
.fill(Color.red)
.frame(width: .infinity, height: height)
HStack {
Spacer()
Rectangle()
.fill(Color.gray)
.frame(width: 100, height: 10)
.cornerRadius(10)
.gesture(
DragGesture()
.onChanged { value in
height = value.translation.height + MIN_HEIGHT
}
)
Spacer()
}
}
}
The correct calculation is
.gesture(
DragGesture()
.onChanged { value in
height = max(MIN_HEIGHT, height + value.translation.height)
}
)
Also, remove the infinite width. It's invalid.
.frame(minHeight: 0, maxHeight: height)
or
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: height)
I want SwiftUI could understand where I am tapping on my app, therefore I gave .onTapGesture possibility to my Views, in this codes a View is bigger than its parent View but I could fix the issue with clip, the issue get fixed visually, but in codes SwiftUI see the Rec in original size, I wanted to know if this miss-behaviour is a bug in SwiftUI, or code we solve it with some magic codes? thanks for all.
gif:
struct ContentView: View {
var body: some View {
ZStack {
Color.white.ignoresSafeArea()
.onTapGesture { print("you tapped on Screen!") }
Circle()
.fill(Color.red)
.frame(width: 300, height: 300, alignment: .center)
.overlay(
Rectangle()
.fill(Color.yellow)
.frame(width: 100, height: UIScreen.main.bounds.height, alignment: .center)
.onTapGesture { print("you tapped on Rectangle!") }
)
.clipShape(Circle())
.onTapGesture { print("you tapped on Circle!") }
}
}
}
Just use contentShape(Circle()) before the onTapGesture and it is fixed
Circle()
.fill(Color.red)
.frame(width: 300, height: 300, alignment: .center)
.overlay(
Rectangle()
.fill(Color.yellow)
.frame(width: 100, height: UIScreen.main.bounds.height, alignment: .center)
.onTapGesture { print("you tapped on Rectangle!") }
)
.clipShape(Circle())
.contentShape(Circle()) //<< contentShape here
.onTapGesture { print("you tapped on Circle!") }
I'm researching SwiftUI to see if it fits our compony's use case. However, I find it rather bare bones and wonder wether I am missing some key insights.
Currently I am researching if V/HStack's can respond dynamically to Accesability font-size changes.
The issue is that I need to give the items a frame, it does not automatically pin the outer Stacks items-view constraints to their inner content. When the user sets the font-size to full blown Bananas-mode the UI just breaks.
struct PatientHistoryView: View {
#State var model : PatientHistoryModel = historyModel;
var body : some View {
GeometryReader { g in
ScrollView(.vertical, showsIndicators: true) {
List
{
ForEach(self.model.data)
{ labResults in
LabDetailView(labeResults: labResults)
}
}.frame(width: 300, height: g.size.height).background(Color.clear)
}
}
}
}
adding these properties do not seem to affect the Font-scaling issues:
struct PatientHistoryView: View {
#State var model : PatientHistoryModel = historyModel;
var body : some View {
GeometryReader { g in
ScrollView(.vertical, showsIndicators: true) {
List
{
ForEach(self.model.data)
{ labResults in
LabDetailView(labeResults: labResults).frame(minWidth: 200, idealWidth: 400, maxWidth: 600, minHeight: 50, idealHeight: 100, maxHeight: 600)
}
}.frame(width: 300, height: g.size.height).background(Color.clear)
}
}
}
}
Here is the labDetailView
struct LabDetailView: View {
var labeResults : LabResults
var color : Color = .red
var spacer : some View {
Spacer().frame(width: 10, height: 0)
}
var body : some View {
GeometryReader { g in
HStack
{
Group
{
VerticalLineView().frame(width: 10, height: g.size.height, alignment: .leading).background(self.color)
self.spacer
self.spacer
LineStack(lineColor: .white, lineHeight: 2).frame(width: 20, height: 20, alignment: .center)
self.spacer
self.spacer
}
VStack(alignment: .leading, spacing: 8)
{
Text(self.labeResults.title).fontWeight(.bold)
Text(self.labeResults.type)
}
Spacer()
VStack(alignment: .center)
{
Text(self.labeResults.max.asString)
Spacer()
Text(self.labeResults.avg.asString).fontWeight(.bold).font(.system(size: 20))
Spacer()
Text(self.labeResults.min.asString)
}.frame(width: 50, height: g.size.height, alignment: .center)
self.spacer
self.spacer
}.frame(width: g.size.width, height: g.size.height, alignment: .leading)
}
}
}
In UIkit these issues can be resolved in the traitCollectionDidChange hook. However, in Swift UI I just cannot find an equivalent.
Doe anybody have any ideas?
Thanks!