I have a Storyboard UIKIT function getAcctBalance(). In my UIKIT file I display a UIHostingController to load a SWIFTUI view. In the SWIFTUI view I want to call my function getAcctBalance().
SWIFTUI:
import SwiftUI
import SWXMLHash
struct UserDashboard: View {
#ObservedObject var userModel: UserDashboardModel
Button(action: {
UserDashboardHostVC.getAcctBalance() //This function call is not working
}){
Text("Load Balance")
}
Text(userModel.totalBalance) //by default it is 0.0 and when button is pressed it should return 599
}
UIKIT:
import UIKit
class UserDashboardModel: ObservableObject {
#Published var totalBalance = 0.0
}
class UserDashboardHostVC: UIViewController {
var userModel = UserDashboardModel()
override func viewDidLoad() {
super.viewDidLoad()
let controller = UIHostingController(rootView: UserDashboard(userModel: self.userModel))
controller.view.translatesAutoresizingMaskIntoConstraints = false
self.addChild(controller)
self.view.addSubview(controller.view)
controller.didMove(toParent: self)
}
func getAcctBalance {
userModel.totalBalance = 599
}
Here is a sample
class UserDashboardModel: ObservableObject {
#Published var totalBalance = 0.0
internal var uIViewController: UserDashboardHostVC? = nil
func getAcctBalance(){
if uIViewController != nil{
uIViewController!.getAcctBalance()
}else{
print("view controller is not connected")
}
}
}
struct UserDashboard: View {
#ObservedObject var userModel: UserDashboardModel
var body: some View{
VStack{
Button(action: {
userModel.getAcctBalance() //Call shared model
}){
Text("Load Balance")
}
Text(userModel.totalBalance.description) //Now it will change
}.frame(width: 400, height: 400, alignment: .center)
}
}
class UserDashboardHostVC: UIViewController {
var userModel = UserDashboardModel()
override func viewDidLoad() {
super.viewDidLoad()
//Crucial connection
userModel.uIViewController = self
let controller = UIHostingController(rootView: UserDashboard(userModel: self.userModel))
self.addChild(controller)
self.view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
//Update constraints per use case
controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
controller.didMove(toParent: self)
}
func getAcctBalance() {
userModel.totalBalance = 599
}
}
Related
So I am using a WKWebView within UIViewRepresentable so I can show a web view in my SwiftUI view.
For a while I could not figure out why my SwiftUI view would not update when the Coordinator would set #Publsihed properties that affect the SwiftUI view.
In the process I finally understood better how UIViewRepresentable works and realized what the problem was.
This is the UIViewRepresentable:
struct SwiftUIWebView : UIViewRepresentable {
#ObservedObject var viewModel: WebViewModel
func makeCoordinator() -> Coordinator {
Coordinator(self, viewModel: viewModel)
}
let webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
....
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
// This made my SwiftUI view update properly when the web view would report loading progress etc..
context.coordinator.viewModel = viewModel
}
}
The SwiftUI view would pass in the viewModel, then makeCoordinator would be called (only the first time at init...), then the Coordinator would be returned with that viewModel.
However, subsequently when a new viewModel was passed in on updates and not on coordinator init, the coordinator would just keep the old viewModel and things would stop working.
So I added this in the updateUIView... call, which did fix the problem:
context.coordinator.viewModel = viewModel
Question:
Is there a way to pass in the viewModel to the Coordinator during the func makeUIView(context: Context) -> WKWebView { ... } so that if a new viewModel is passed in to SwiftUIWebView the coordinator would also get the change automatically instead of me having to add:
context.coordinator.viewModel = viewModel
in updateUIView...?
EDIT:
Here is the entire code. The root content view:
struct ContentView: View {
#State var showTestModal = false
#State var redrawTest = false
var body: some View {
NavigationView {
VStack {
Button(action: {
showTestModal.toggle()
}) {
Text("Show modal")
}
if redrawTest {
Text("REDRAW")
}
}
}
.fullScreenCover(isPresented: $showTestModal) {
WebContentViewTest(redraw: $redrawTest)
}
}
}
And what the Content view presents:
struct SwiftUIProgressBar: View {
#Binding var progress: Double
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(Color.gray)
.opacity(0.3)
.frame(width: geometry.size.width, height: geometry.size.height)
Rectangle()
.foregroundColor(Color.blue)
.frame(width: geometry.size.width * CGFloat((self.progress)),
height: geometry.size.height)
.animation(.linear(duration: 0.5))
}
}
}
}
struct SwiftUIWebView : UIViewRepresentable {
#ObservedObject var viewModel: WebViewModel
func makeCoordinator() -> Coordinator {
Coordinator(self, viewModel: viewModel)
}
let webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
print("SwiftUIWebView MAKE")
if let url = URL(string: viewModel.link) {
self.webView.load(URLRequest(url: url))
}
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
//add your code here...
}
}
class Coordinator: NSObject {
private var viewModel: WebViewModel
var parent: SwiftUIWebView
private var estimatedProgressObserver: NSKeyValueObservation?
init(_ parent: SwiftUIWebView, viewModel: WebViewModel) {
print("Coordinator init")
self.parent = parent
self.viewModel = viewModel
super.init()
estimatedProgressObserver = self.parent.webView.observe(\.estimatedProgress, options: [.new]) { [weak self] webView, _ in
print(Float(webView.estimatedProgress))
guard let weakSelf = self else{return}
print("in progress observer: model is: \(Unmanaged.passUnretained(weakSelf.parent.viewModel).toOpaque())")
weakSelf.parent.viewModel.progress = webView.estimatedProgress
}
}
deinit {
estimatedProgressObserver = nil
}
}
class WebViewModel: ObservableObject {
#Published var progress: Double = 0.0
#Published var link : String
init (progress: Double, link : String) {
self.progress = progress
self.link = link
print("model init: \(Unmanaged.passUnretained(self).toOpaque())")
}
}
struct WebViewContainer: View {
#ObservedObject var model: WebViewModel
var body: some View {
ZStack {
SwiftUIWebView(viewModel: model)
VStack {
if model.progress >= 0.0 && model.progress < 1.0 {
SwiftUIProgressBar(progress: .constant(model.progress))
.frame(height: 15.0)
.foregroundColor(.accentColor)
}
Spacer()
}
}
}
}
struct WebContentViewTest : View {
#Binding var redraw:Bool
var body: some View {
let _ = print("WebContentViewTest body")
NavigationView {
ZStack(alignment: .topLeading) {
if redraw {
WebViewContainer(model: WebViewModel(progress: 0.0, link: "https://www.google.com"))
}
VStack {
Button(action: {
redraw.toggle()
}) {
Text("redraw")
}
Spacer()
}
}
.navigationBarTitle("Test Modal", displayMode: .inline)
}
}
}
If you run this you will see that while WebViewModel can get initialized multiple times, the coordinator will only get initialized once and the viewModel in it does not get updated. Because of that, things break after the first redraw.
i have a ScrollView from UIKit and use it for SwiftUI: Is there any way to make a paged ScrollView in SwiftUI?
Question: How can I scroll in the UIScrollView to a position with a button click on a button in a SwiftUI View OR what is also good for my needs to scroll to a position when first displaying the ScrollView
I tried contentOffset but this didnt work. Perhaps I've done something wrong.
ScrollViewWrapper:
class UIScrollViewViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let v = UIScrollView()
v.isPagingEnabled = false
v.alwaysBounceVertical = true
return v
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.pinEdges(of: self.hostingController.view, to: self.scrollView)
self.hostingController.didMove(toParent: self)
}
func pinEdges(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
])
}
}
struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> UIScrollViewViewController {
let vc = UIScrollViewViewController()
vc.hostingController.rootView = AnyView(self.content())
return vc
}
func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
viewController.hostingController.rootView = AnyView(self.content())
}
}
SwiftUI usage:
struct ContentView: View{
#ObservedObject var search = SearchBar()
var body: some View{
NavigationView{
GeometryReader{geo in
UIScrollViewWrapper{ //<-----------------
VStack{
ForEach(0..<10){i in
Text("lskdfj")
}
}
.frame(width: geo.size.width)
}
.navigationBarTitle("Test")
}
}
}
}
We will first declare the offset property in the UIViewControllerRepresentable, with the propertyWrapper #Binding, because its value can be changed by the scrollview or by the parent view (the ContentView).
struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
#Binding var offset: CGPoint
init(offset: Binding<CGPoint>, #ViewBuilder content: #escaping () -> Content) {
self.content = content
_offset = offset
}
// ....//
}
If the offset changes cause of the parent view, we must apply these changes to the scrollView in the updateUIViewController function (which is called when the state of the view changes) :
func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
viewController.hostingController.rootView = AnyView(content())
viewController.scrollView.contentOffset = offset
}
When the offset changes because the user scrolls, we must reflect this change on our Binding. To do this we must declare a Coordinator, which will be a UIScrollViewDelegate, and modify the offset in its scrollViewDidScroll function :
class Controller: NSObject, UIScrollViewDelegate {
var parent: UIScrollViewWrapper<Content>
init(parent: UIScrollViewWrapper<Content>) {
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
parent.offset = scrollView.contentOffset
}
}
and, in struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable
func makeCoordinator() -> Controller {
return Controller(parent: self)
}
Finally, for the initial offset (this is important otherwise your starting offset will always be 0), this happens in the makeUIViewController:
you have to add these lines:
vc.view.layoutIfNeeded ()
vc.scrollView.contentOffset = offset
The final project :
import SwiftUI
struct ContentView: View {
#State private var offset: CGPoint = CGPoint(x: 0, y: 200)
let texts: [String] = (1...100).map {_ in String.random(length: Int.random(in: 6...20))}
var body: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
UIScrollViewWrapper(offset: $offset) { //
VStack {
Text("Start")
.foregroundColor(.red)
ForEach(texts, id: \.self) { text in
Text(text)
}
}
.padding(.top, 40)
.frame(width: geo.size.width)
}
.navigationBarTitle("Test")
}
HStack {
Text(offset.debugDescription)
Button("add") {
offset.y += 100
}
}
.padding(.bottom, 10)
.frame(maxWidth: .infinity)
.background(Color.white)
}
}
}
class UIScrollViewViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let v = UIScrollView()
v.isPagingEnabled = false
v.alwaysBounceVertical = true
return v
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
pinEdges(of: scrollView, to: view)
hostingController.willMove(toParent: self)
scrollView.addSubview(hostingController.view)
pinEdges(of: hostingController.view, to: scrollView)
hostingController.didMove(toParent: self)
}
func pinEdges(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
])
}
}
struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
#Binding var offset: CGPoint
init(offset: Binding<CGPoint>, #ViewBuilder content: #escaping () -> Content) {
self.content = content
_offset = offset
}
func makeCoordinator() -> Controller {
return Controller(parent: self)
}
func makeUIViewController(context: Context) -> UIScrollViewViewController {
let vc = UIScrollViewViewController()
vc.scrollView.contentInsetAdjustmentBehavior = .never
vc.hostingController.rootView = AnyView(content())
vc.view.layoutIfNeeded()
vc.scrollView.contentOffset = offset
vc.scrollView.delegate = context.coordinator
return vc
}
func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
viewController.hostingController.rootView = AnyView(content())
viewController.scrollView.contentOffset = offset
}
class Controller: NSObject, UIScrollViewDelegate {
var parent: UIScrollViewWrapper<Content>
init(parent: UIScrollViewWrapper<Content>) {
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
parent.offset = scrollView.contentOffset
}
}
}
You will need to pass a #Binding var offset: CGPoint into the UIScrollViewWrapper then when the button is clicked in your SwiftUI view, you can update the binding value which can then be used in the update method for UIViewControllerRepresentable. Another idea is to use UIViewRepresentable instead and use that with UIScrollView. Here is a helpful article doing that and setting its offset: https://www.fivestars.blog/articles/scrollview-offset/.
Rather than selecting from a list, I'm trying to navigate between drawings like a book by using buttons to cycle through, but the canvas doesn't update.
I'm following the great tutorial by DevTechie at https://www.youtube.com/watch?v=amZH2i6l004&list=PLbrKvTeCrFAfoACvHOPWFmDIaKUqBZgEr&index=5
The github repo is at https://github.com/devtechie/DrawingDocuments
Here's my ContentView and my version of the DrawingWrapper. The DrawingWrapper uses a DrawingManager (SwiftUI) to pull from CoreData and the DrawingViewController to define a PKCanvas. I wasn't sure which delegate to use and really struggling understanding how to refresh the canvas.
ContentView
struct ContentView: View {
#StateObject var manager = DrawingManager()
#State var addNewShown = false
#State var pageNumber: Int = 0
#State var newVar = UUID()
var body: some View {
VStack{
Text(manager.docs[pageNumber].name!)
HStack{
Button(action:{
pageNumber -= 1
newVar = manager.docs[pageNumber].id!
//desiredDoc = manager.docs[pageNumber]
}){
Image(systemName: "chevron.left")
}
Spacer()
Button(action:{
pageNumber += 1
newVar = manager.docs[pageNumber].id!
//desiredDoc = manager.docs[pageNumber]
}){
Image(systemName: "chevron.right")
}
}
}
}
DrawingWrapper
struct DrawingWrapper: UIViewControllerRepresentable {
var manager: DrawingManager
#Binding var doc: DrawingDoc
typealias UIViewControllerType = DrawingViewController
class Coordinator: NSObject, PKCanvasViewDelegate {
var parent: DrawingWrapper
init(_ parent: DrawingWrapper){
self.parent = parent
}
func canvasViewDidFinishRendering(_ canvasView: PKCanvasView) {
if let uiDrawing = canvasView.drawing as? PKDrawing {
parent.doc.data = uiDrawing.dataRepresentation()
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<DrawingWrapper>) -> DrawingWrapper.UIViewControllerType {
let viewController = DrawingViewController()
viewController.drawingData = doc.data!
viewController.drawingChanged = {data in
manager.update(data: data, for: doc.id!)
}
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: DrawingViewController, context: UIViewControllerRepresentableContext<DrawingWrapper>) {
uiViewController.drawingData = doc.data!
}
}
I am trying to get my own custom button floating over a GMSMapView. The button draws on the GMSMapView, but the button action is not triggered. The Floating button is written in SwiftUI and this is it:
struct MyLocationView: View {
#ObservedObject var viewController: ViewController
var body: some View {
ZStack {
Button(action: {
print("Hello")
self.viewController.myLocationButtonPressed = !self.viewController.myLocationButtonPressed
}) {
ZStack {
Circle()
.foregroundColor(.black)
.frame(width: 60, height: 60)
.shadow(radius: 10)
Image(systemName: viewController.myLocationButtonPressed ? "location.fill" : "location")
.foregroundColor(.blue)
}
}
}
}
}
This is my viewController
import UIKit
import SwiftUI
import GoogleMaps
class ViewController: UIViewController, ObservableObject {
#Published var myLocationButtonPressed: Bool = false
#IBOutlet var mapView: GMSMapView!
override func viewDidLoad() {
super.viewDidLoad()
// My Location Button
let myLocationView = MyLocationView(viewController: self)
let hostingController = UIHostingController(rootView: myLocationView)
hostingController.view.backgroundColor = .blue
addChild(hostingController)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
Any idea why the button action is not working here?
This is for MacOS. I am trying to figure out how I can pump values to my model while the user holds down a custom button. Basically I am trying to recreate a MouseDown/MouseUp combo with a timer firing in between. It doesn’t seem possible with just a LongPressGesture so I have been experimenting with Combine and a Timer but with only partial success. I have the following:
import SwiftUI
import Combine
var cancellable: Cancellable?
struct ContentView: View {
var body: some View {
ZStack{
FastForwardButton().frame(width: 40, height: 40)
}.frame(width: 200, height: 200)
}
}
struct FastForwardButton: View {
var timer = Timer.publish(every: 0.2, on: .main, in: .common)
#GestureState private var isPressed = false
// #State var cancellable: Cancellable?
var body: some View {
Rectangle()
.gesture(
LongPressGesture(minimumDuration: 4)
.updating($isPressed, body: { (currentState, state, transaction) in
if self.isPressed == false{
state = currentState
print("Timer Started")
cancellable = self.timer.connect()
}
})
)
.onReceive(timer) { time in
// Do Something here eg myModel.pump()
print("The time is \(time)")
if self.isPressed == false{
print("Timer Cancelled")
cancellable?.cancel()
// cancellable = nil
}
}
}
}
The above works one time. I get:
"Timer Started"
"The Time is xxx"
.....
"The Time is xxx"
"Timer Cancelled"
But the second press I just get:
"Timer Started"
With no further actions
Note I also had to temporarily move the reference to cancellable outside the View as I got a warning about modifying the View while updating.
Can anyone figure out why the .onReceive closure is only called once? Thanks!
try this (copy - paste - run) macOS
import SwiftUI
import Combine
class Model: ObservableObject {
var timerPublisher: Timer.TimerPublisher?
var handle: AnyCancellable?
var startstop: Cancellable?
func start() {
timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
handle = timerPublisher?.sink(receiveValue: {
print($0)
})
startstop = timerPublisher?.connect()
print("start")
}
func stop() {
startstop?.cancel()
print("stop")
}
}
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
VStack {
Button(action: {
self.model.start()
}) {
Text("Start")
}
Button(action: {
self.model.stop()
}) {
Text("Stop")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
it shows two buttons and you can start stop publishing ... (Apsperi is right that once publisher is canceled, it is not usable again. It is true for any publisher, not specific for Timer.Publisher.
if you like to "publish" some changes in your model you can do it some standard way
func start() {
timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
handle = timerPublisher?.sink(receiveValue: {
print($0)
self.objectWillChange.send()
})
startstop = timerPublisher?.connect()
print("start")
}
The question is why not simply use .autoconnect() ?? The answer is flexibility, think about this model
class Model: ObservableObject {
var timerPublisher: Timer.TimerPublisher?
var handle: AnyCancellable?
var startstop: Cancellable?
func createTimerPublisher() {
timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
handle = timerPublisher?.sink(receiveValue: {
print($0)
self.objectWillChange.send()
})
}
init() {
createTimerPublisher()
}
func start() {
startstop = timerPublisher?.connect()
print("start")
}
func stop() {
startstop?.cancel()
print("stop")
// or create it conditionaly for later use
createTimerPublisher()
}
}
UPDATE where mouse-up and mouse-down on some View starts / stops the timer
import SwiftUI
import Combine
class Model: ObservableObject {
var timerPublisher: Timer.TimerPublisher?
var handle: AnyCancellable?
var startstop: Cancellable?
func createTimerPublisher() {
timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
handle = timerPublisher?.sink(receiveValue: {
print($0)
self.objectWillChange.send()
})
}
init() {
createTimerPublisher()
}
func start() {
// if
startstop = timerPublisher?.connect()
print("start")
}
func stop() {
startstop?.cancel()
startstop = nil
print("stop")
// or create it conditionaly for later use
createTimerPublisher()
}
}
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
VStack {
Button(action: {
self.model.start()
}) {
Text("Start")
}
Button(action: {
self.model.stop()
}) {
Text("Stop")
}
MouseUpDownRepresentable(content: Rectangle()).environmentObject(model)
.frame(width: 50, height: 50)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
class MouseUpDownViewClass<Content>: NSHostingView<Content> where Content : View {
let model: Model
required init(model: Model, rootView: Content) {
self.model = model
super.init(rootView: rootView)
}
#objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
required init(rootView: Content) {
fatalError("init(rootView:) has not been implemented")
}
override func mouseUp(with event: NSEvent) {
print("mouse up")
model.stop()
}
override func mouseDown(with event: NSEvent) {
print("mouse down")
model.start()
}
}
struct MouseUpDownRepresentable<Content>: NSViewRepresentable where Content: View {
#EnvironmentObject var model: Model
let content: Content
func makeNSView(context: Context) -> NSHostingView<Content> {
return MouseUpDownViewClass(model: model, rootView: self.content)
}
func updateNSView(_ nsView: NSHostingView<Content>, context: Context) {
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If it looks too complex, you can create it solely with SwiftUI gesture. The trick uses different gesture recognition
struct TouchView: View {
#State var pressed = false
var body: some View {
Circle().fill(pressed ? Color.yellow : Color.orange)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged({ (touch) in
if self.pressed == false {
self.pressed = true
print("start")
}
})
.onEnded({ (touch) in
print("stop")
self.pressed = false
})
)
}
}