How to use drag and drop gesture in SwiftUI? - swift

I am trying to make a program by using SwiftUI in which you can just drag a bunch of squares along. Here, there are two squares namely X and Y.
Everything works fine until you drag the squares along. After you drag and drop the first square the second square immediately follows it. I am not sure why this happens. I tried making every single constant and variable in these squares different but still after you drag and drop one of the squares the second just follows along.
My code is:
struct DemoSceneKit: View {
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
case dragging2(translation2: CGSize)
var translation: CGSize {
switch self {
case .inactive, .pressing, .dragging2:
return .zero
case .dragging(let translation):
return translation
}
}
var translation2: CGSize {
switch self {
case .inactive, .pressing, .dragging:
return .zero
case .dragging2(let translation2):
return translation2
}
}
var isActive: Bool {
switch self {
case .inactive:
return false
case .pressing, .dragging, .dragging2:
return true
}
}
var isDragging: Bool {
switch self {
case .inactive, .pressing:
return false
case .dragging, .dragging2:
return true
}
}
}
#GestureState var dragState2 = DragState.inactive
#GestureState var dragState = DragState.inactive
#State var viewState = CGSize.zero
var body: some View {
//First Square
let minimumLongPressDuration = 0.2
let longPressDrag = LongPressGesture(minimumDuration: minimumLongPressDuration)
.sequenced(before: DragGesture())
.updating($dragState) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag):
state = .dragging(translation: drag?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let drag?) = value else { return }
self.viewState.width += drag.translation.width
self.viewState.height += drag.translation.height
}
//Second square
let longPressDrag2 = LongPressGesture(minimumDuration: minimumLongPressDuration)
.sequenced(before: DragGesture())
.updating($dragState2) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag2):
state = .dragging2(translation2: drag2?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let drag2?) = value else { return }
self.viewState.width += drag2.translation.width
self.viewState.height += drag2.translation.height
}
return
ZStack{
// First Square
Rectangle()
.stroke(Color.white, lineWidth: 5)
.frame(width: 100, height: 100)
.offset(
x: viewState.width + dragState.translation.width,
y: viewState.height + dragState.translation.height
)
.gesture(longPressDrag)
Text("X")
.offset(x: viewState.width + dragState.translation.width,
y: viewState.height + dragState.translation.height
)
.gesture(longPressDrag)
.frame(width:200, height: 200)
// Second Square
ZStack{
Rectangle()
.stroke(Color.blue, lineWidth: 5)
.frame(width: 100, height: 100)
.position(x: 350, y: 300)
.offset(
x: viewState.width + dragState2.translation.width,
y: viewState.height + dragState2.translation.height
)
.gesture(longPressDrag2)
Text("Y")
.offset(x: viewState.width + dragState2.translation.width,
y: viewState.height + dragState2.translation.height
)
.gesture(longPressDrag2)
.frame(width:400, height: 300)
.position(x: 350, y: 300)
}
}
}
}
Please help me with this code.

Related

SwiftUI, apply drawGesture only when over Image

I'm try to apply the gesture only when the user is over the Image display not when tapped outside the image.
Any suggestion how I can do? this following code draw also when user tap outside the image.
struct ContentView: View {
#StateObject var am = AppManager()
#State var switchToTakePicture = false
#State var paths: [PathContainer] = []
#State var currentDraggingId = UUID()
#State var spikoPic = #imageLiteral(resourceName: "spiko-16.jpeg")
#State var centerOFimage = CGSize(width: 0, height: 0)
var body: some View {
GeometryReader { proxy in
ZStack {
Image(uiImage: spikoPic)
.resizable()
.scaledToFit()
.position(x: proxy.size.width/2, y: proxy.size.height/2)
.background(GeometryReader {
Color.clear.preference(key: ViewRectKey.self,
value: [$0.frame(in: .global)])
})
.gesture(drawGesture) // not correct
ForEach(paths) { container in
// draw and set the foreground color of the paths to red
container.path
.stroke(Color.red, lineWidth: 4)
}
}
.onPreferenceChange(ViewRectKey.self) { rects in
print(rects.first ?? .zero)
}
}
}
var drawGesture: some Gesture {
DragGesture(minimumDistance: 0)
.onChanged { value in
// The point that the gesture started from
let start = value.startLocation
// The point that the gesture ended to
let end = value.location
// the properties of the rectangle to be drawn
let rectangle: CGRect = .init(origin: end,
size: .init(width: start.x - end.x,
height: start.y - end.y))
// create a path for the rectangle
let path: Path = .init { path in
path.addRect(rectangle)
}
print("rettangolo = \(rectangle) orig \(rectangle.origin) - height \(rectangle.height) width = \(rectangle.width)")
// remove the previous rectangle that was drawen in current
// process of drawing
paths.removeAll { $0.id == currentDraggingId }
// append the new rectangle
paths.append(.init(id: currentDraggingId, path: path))
}
.onEnded { _ in
// renew the dragging id so the app know that the next
// drag gesture is drawing a completely new rectangle,
// and is not continuing the drawing of the last rectangle
currentDraggingId = .init()
}
}
}
i want no box outside

Combined gesture never stops updating itself

I would like to create a gesture which would require LongTapGesture before one of other two gestures (DragGesture and MagnificationGesture) is possible. It works fine for just dragging, but if you try to magnify it soon ends in a hangup state - looks like magnification never ends.
What would be the right way to do it?
Here's the code:
struct DraggableCircle: View {
enum DragState {
case inactive
case pressing
case draggingOrMagnifying(translation: CGSize, magnification: CGFloat)
var translation: CGSize {
switch self {
case .inactive, .pressing:
return .zero
case .draggingOrMagnifying(let translation, _):
return translation
}
}
var magnification: CGFloat {
switch self {
case .inactive, .pressing:
return 1
case .draggingOrMagnifying(_, let magnification):
return magnification
}
}
var isActive: Bool {
switch self {
case .inactive:
return false
case .pressing, .draggingOrMagnifying:
return true
}
}
var isDragging: Bool {
switch self {
case .inactive, .pressing:
return false
case .draggingOrMagnifying:
return true
}
}
}
#GestureState var dragState = DragState.inactive
#State var viewState = CGSize.zero
#State var magState = CGFloat(1)
var body: some View {
let minimumLongPressDuration = 0.5
let longPressDrag = LongPressGesture(minimumDuration: minimumLongPressDuration)
.sequenced(before: DragGesture().exclusively(before: MagnificationGesture()))
.updating($dragState) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let exclusiveGesture):
switch exclusiveGesture {
case .first(let drag):
state = .draggingOrMagnifying(translation: drag.translation, magnification: 1)
case .second(let mag):
state = .draggingOrMagnifying(translation: .zero, magnification: mag)
case .none:
state = .draggingOrMagnifying(translation: .zero, magnification: 1)
}
print(state)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let gest?) = value else { return }
switch gest {
case .first(let drag):
self.viewState.width += drag.translation.width
self.viewState.height += drag.translation.height
case .second(let mag):
self.magState = magState * mag
}
}
return Circle()
.fill(Color.blue)
.overlay(dragState.isDragging ? Circle().stroke(Color.white, lineWidth: 2) : nil)
.frame(width: clipToBounds(100 * magState * dragState.magnification), height: clipToBounds(100 * magState * dragState.magnification), alignment: .center)
.offset(
x: viewState.width + dragState.translation.width,
y: viewState.height + dragState.translation.height
)
.animation(nil)
.shadow(radius: dragState.isActive ? 8 : 0)
.animation(.linear(duration: minimumLongPressDuration))
.gesture(longPressDrag)
}
func clipToBounds(_ m: CGFloat) -> CGFloat {
if abs(m) > UIScreen.main.bounds.height {
return UIScreen.main.bounds.height
} else {
return abs(m)
}
}
}

Restarting perpetual animation after stopping in SwiftUI

Background
In this learning app, I've followed and excellent tutorial from Hacking with Swift on generating a wave-like animation. I've modified this app further adding some functionalities:
Providing Start/Stop mechanism for the wave animation
Perpetually generating random numbers for the duration of the animation
Modifying animation if an "interesting" number is found. Initially, I've implemented logic that defines even numbers as interesting but that could be easily changes to flag prime numbers, etc.
Problem
After stopping the animation does not "run" again. This is demonstrated in the gif below.
After stopping the animation does not restart.
Code
//
// ContentView.swift
// WaveExample
//
// Created by Konrad on 28/07/2021.
// Original tutorial: https://www.hackingwithswift.com/plus/custom-swiftui-components/creating-a-waveview-to-draw-smooth-waveforms
//
import SwiftUI
/**
Creates wave shape object
- Parameter strength: How tall the wave should be
- Parameter frequency: How densly the wave should be packed
- returns: Shape
*/
struct Wave: Shape {
// Basic wave characteristics
var strength: Double // Height
var frequency: Double // Number of hills
var phase: Double // Offsets the wave, can be used to animate the view
// Required to define that animation relates to moving the wave from left to right
var animatableData: Double {
get { phase }
set { self.phase = newValue }
}
// Path drawing function
func path(in rect: CGRect) -> Path {
let path = UIBezierPath()
// Basic waveline characteristics
let width = Double(rect.width)
let height = Double(rect.height)
let midWidth = width / 2
let midHeight = height / 2
let wavelength = width / frequency
let oneOverMidWidth = 1 / midWidth
// Path characteristics
path.move(to: CGPoint(x: 0, y: midHeight))
// By determines the nmber of calculations, can be decreased to run faster
for xPosition in stride(from: 0, through: width, by: 1) {
let relativeX = xPosition / wavelength // How far we are from the start point
let distanceFromMidWidth = xPosition - midWidth // Distance from the middle of the space
let normalDistance = distanceFromMidWidth * oneOverMidWidth // Get values from -1 to 1, normalize
// let parabola = normalDistance // Small waves in the middle
let parabola = -(normalDistance * normalDistance) + 1 // Big wave in the middle
let sine = sin(relativeX + phase) // Offset based on phase
let yPosition = parabola * strength * sine + midHeight // Moving this halfway
path.addLine(to: CGPoint(x: xPosition, y: yPosition))
}
return Path(path.cgPath)
}
}
struct Line: Shape {
func path(in rect: CGRect) -> Path {
// Positioning
let midHeight = rect.height / 2
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: midHeight))
path.addLine(to: CGPoint(x: rect.width, y: midHeight))
return Path(path.cgPath)
}
}
struct ContentView: View {
#State private var phase = 0.0 // Used to animate the wave
#State private var waveStrength: Double = 10.0 // How tall, change for interesting numbers
#State private var waveFrequency: Double = 10.0 // How frequent, change for interesting numbers
#State var isAnimating: Bool = false // Currently running animation
#State private var randNum: Int16 = 0 // Random number to keep generating while animating
#State private var isNumberInteresting: Bool = false // Will take 'true' of the random number has some interesting properties
// Timer publisher reflecting frequent animation changes
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
// Stop timer
func stopTimer() {
self.timer.upstream.connect().cancel()
}
// Start timer
func startTimer() {
self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
// Check if number is interesting
func checkNumber(num: Int16) -> Bool {
var isInteresting: Bool = false
if num % 2 == 0 {
isInteresting.toggle()
}
return isInteresting
}
var body: some View {
VStack {
if self.isAnimating {
VStack {
Button("Stop") {
self.isAnimating = false
stopTimer()
}
.font(.title)
.foregroundColor(Color(.blue))
Text("Random number: \(String(randNum)), interesting: \(String(isNumberInteresting))")
.onReceive(timer, perform: { _ in
randNum = Int16.random(in: 0..<Int16.max)
isNumberInteresting = checkNumber(num: randNum)
})
}
} else {
Button("Start") {
self.isAnimating = true
startTimer()
}
.font(.title)
.foregroundColor(Color(.red))
}
if self.isAnimating {
// Animation
ZStack {
ForEach(0..<10) { waveIteration in
Wave(strength: waveStrength, frequency: waveFrequency, phase: phase)
.stroke(Color.blue.opacity(Double(waveIteration) / 3), lineWidth: 1.1)
.offset(y: CGFloat(waveIteration) * 10)
}
}
.onReceive(timer) { _ in
// withAnimation requires info on how to animate
withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
self.phase = .pi * 2 // 180 degrees of sine being calculated
if isNumberInteresting {
waveFrequency = 50.0
waveStrength = 50.0
} else {
waveFrequency = 10.0
waveStrength = 10.0
}
}
}
.frame(height: UIScreen.main.bounds.height * 0.8)
} else {
// Static line
ZStack {
Line()
.stroke(Color.blue)
}
.frame(height: UIScreen.main.bounds.height * 0.8)
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Side notes
In addiction to the problem above any good practice pointers on working with Swift are always welcome.
I made your project works, you can see the changed code // <<: Here!, the issue was there that you did not show the Animation the changed value! you showed just one time! and after that you keep it the same! if you see your code in your question you are repeating self.phase = .pi * 2 it makes no meaning to Animation! I just worked on your ContentView the all project needs refactor work, but that is not the issue here.
struct ContentView: View {
#State private var phase = 0.0
#State private var waveStrength: Double = 10.0
#State private var waveFrequency: Double = 10.0
#State var isAnimating: Bool = false
#State private var randNum: Int16 = 0
#State private var isNumberInteresting: Bool = false
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var stringOfText: String = String() // <<: Here!
func stopTimer() {
self.timer.upstream.connect().cancel()
phase = 0.0 // <<: Here!
}
func startTimer() {
self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(500)) { phase = .pi * 2 } // <<: Here!
}
func checkNumber(num: Int16) -> Bool {
var isInteresting: Bool = false
if num % 2 == 0 {
isInteresting.toggle()
}
return isInteresting
}
var body: some View {
VStack {
Button(isAnimating ? "Stop" : "Start") { // <<: Here!
isAnimating.toggle() // <<: Here!
isAnimating ? startTimer() : stopTimer() // <<: Here!
}
.font(.title)
.foregroundColor(isAnimating ? Color.red : Color.blue) // <<: Here!
ZStack {
if isAnimating {
ForEach(0..<10) { waveIteration in
Wave(strength: waveStrength, frequency: waveFrequency, phase: phase)
.stroke(Color.blue.opacity(Double(waveIteration) / 3), lineWidth: 1.1)
.offset(y: CGFloat(waveIteration) * 10)
}
}
else {
Line().stroke(Color.blue)
}
}
.frame(height: UIScreen.main.bounds.height * 0.8)
.overlay(isAnimating ? Text(stringOfText) : nil, alignment: .top) // <<: Here!
.onReceive(timer) { _ in
if isAnimating { // <<: Here!
randNum = Int16.random(in: 0..<Int16.max)
isNumberInteresting = checkNumber(num: randNum)
stringOfText = "Random number: \(String(randNum)), interesting: \(String(isNumberInteresting))" // <<: Here!
if isNumberInteresting {
waveFrequency = 50.0
waveStrength = 50.0
} else {
waveFrequency = 10.0
waveStrength = 10.0
}
}
else {
stopTimer() // For safety! Killing Timer in case! // <<: Here!
}
}
.animation(nil, value: stringOfText) // <<: Here!
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) // <<: Here!
.id(isAnimating) // <<: Here!
}
}
}

Trying to return different Shapes in a func. Error: Function declares an opaque return type [duplicate]

As a part of my learning SwiftUI project I do some shape rotations and I have code below. I'm wondering how to avoid of same three lines of modifiers for each shape.
func getShape(shape: Int, i: Int) -> AnyView {
switch shape {
case 0:
return AnyView(Rectangle()
.stroke(colors[Int(shapeColor)])
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
case 1:
return AnyView(Capsule()
.stroke(colors[Int(shapeColor)])
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
case 2:
return AnyView(Ellipse()
.stroke(colors[Int(shapeColor)])
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
default:
return AnyView(Rectangle()
.stroke(colors[Int(shapeColor)])
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
}
}
Using the helper AnyShape type eraser
struct AnyShape: Shape {
private let builder: (CGRect) -> Path
init<S: Shape>(_ shape: S) {
builder = { rect in
let path = shape.path(in: rect)
return path
}
}
func path(in rect: CGRect) -> Path {
return builder(rect)
}
}
your function can be written as
func getShape(shape: Int, i: Int) -> some View {
let selectedShape: AnyShape = {
switch shape {
case 0:
return AnyShape(Rectangle())
case 1:
return AnyShape(Capsule())
case 2:
return AnyShape(Ellipse())
default:
return AnyShape(Rectangle())
}
}()
return selectedShape
.stroke(colors[Int(shapeColor)])
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
}
You can abstract some of the repetition away by using helper functions and extensions.
In the simplified example below I use a #ViewBuilder to clean up the code that we are returning. There is no need to use AnyView and it makes the code much easier to read.
It would be great if we could return some Shape however this is
not currently possible and results in errors. This is why the stroke
value has to be repeated for each Shape in the getShape
function, otherwise we could have made the extension on Shape
instead of View.
I create an extension on View that allows us to combine the modifiers into one function making it more readable and easier to use. Though honestly this part is optional and you could use your two modifiers frame and rotationEffect.
The #ViewBuilder getShape(shape:index:) returns the the shape you have picked with its chosen color, this is then used by the function createShape(shape:index:) where you add the custom modifier that we created as an extension on View.
Finally we create our shape
This should give you a starting point.
struct ShapeView: View {
#ViewBuilder // 1
func getShape(shape: Int, i: Int) -> some View {
switch shape {
case 0:
Rectangle().stroke(Color.red)
case 1:
Capsule().stroke(Color.red)
case 2:
Ellipse().stroke(Color.red)
default:
Rectangle().stroke(Color.red)
}
}
func createShape(shape: Int, index: Int) -> some View { // 3
getShape(shape: shape, i: index)
.myModifier(width: 200, height: 100, index: index, angleStep: 30)
}
var body: some View {
createShape(shape: 2, index: 1) // 4
}
}
// 2
extension View {
func myModifier(width: CGFloat, height: CGFloat, index: Int, angleStep: Double) -> some View {
self
.frame(width: width, height: height)
.rotationEffect(Angle(degrees: Double(index) * Double(angleStep)))
}
}
struct ShapeView_Previews: PreviewProvider {
static var previews: some View {
ShapeView()
}
}
It is such a shame that we cannot return some Shape from the #ViewBuilder or if there existed a #ShapeBuilder as this would mean that we wouldn't have to add the stroke to each shape individually, as a View cannot have a stroke.
A nested function can help cleaning up the code:
func getShape(shape: Int, i: Int) -> some View {
func adjustedView<S: Shape>(shape: S) -> some View {
shape
.stroke(colors[Int(shapeColor)])
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
}
return Group {
switch shape {
case 0:
adjustedView(shape: Rectangle())
case 1:
adjustedView(shape: Capsule())
case 2:
adjustedView(shape: Ellipse())
default:
adjustedView(shape: Rectangle())
}
}
}
Another option is to extend Shape with a convenience function. I.e.
extension Shape {
func adjust(shapeWidth: Double, shapeHeight: Double, angle: Angle) -> some View {
self.stroke()
//.stroke(colors[Int(shapeColor)]) // for brevity
.frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
.rotationEffect(angle)
}
}
It simplifies code a bit. Also there is no need to erase types.
func getShape(shape: Int, i: Int) -> some View {
Group {
switch shape {
case 0:
Rectangle().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
case 1:
Capsule().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
case 2:
Ellipse().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
default:
Rectangle().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
}
}
}

Swiftui getting an image's displaying dimensions

I'm trying to get the dimensions of a displayed image to draw bounding boxes over the text I have recognized using apple's Vision framework.
So I run the VNRecognizeTextRequest uppon the press of a button with this funcion
func readImage(image:NSImage, completionHandler:#escaping(([VNRecognizedText]?,Error?)->()), comp:#escaping((Double?,Error?)->())) {
var recognizedTexts = [VNRecognizedText]()
var rr = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
let requestHandler = VNImageRequestHandler(cgImage: image.cgImage(forProposedRect: &rr, context: nil, hints: nil)!
, options: [:])
let textRequest = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { completionHandler(nil,error)
return
}
for currentObservation in observations {
let topCandidate = currentObservation.topCandidates(1)
if let recognizedText = topCandidate.first {
recognizedTexts.append(recognizedText)
}
}
completionHandler(recognizedTexts,nil)
}
textRequest.recognitionLevel = .accurate
textRequest.recognitionLanguages = ["es"]
textRequest.usesLanguageCorrection = true
textRequest.progressHandler = {(request, value, error) in
comp(value,nil)
}
try? requestHandler.perform([textRequest])
}
and compute the bounding boxes offsets using this struct and function
struct DisplayingRect:Identifiable {
var id = UUID()
var width:CGFloat = 0
var height:CGFloat = 0
var xAxis:CGFloat = 0
var yAxis:CGFloat = 0
init(width:CGFloat, height:CGFloat, xAxis:CGFloat, yAxis:CGFloat) {
self.width = width
self.height = height
self.xAxis = xAxis
self.yAxis = yAxis
}
}
func createBoundingBoxOffSet(recognizedTexts:[VNRecognizedText], image:NSImage) -> [DisplayingRect] {
var rects = [DisplayingRect]()
let imageSize = image.size
let imageTransform = CGAffineTransform.identity.scaledBy(x: imageSize.width, y: imageSize.height)
for obs in recognizedTexts {
let observationBounds = try? obs.boundingBox(for: obs.string.startIndex..<obs.string.endIndex)
let rectangle = observationBounds?.boundingBox.applying(imageTransform)
print("Rectange: \(rectangle!)")
let width = rectangle!.width
let height = rectangle!.height
let xAxis = rectangle!.origin.x - imageSize.width / 2 + rectangle!.width / 2
let yAxis = -(rectangle!.origin.y - imageSize.height / 2 + rectangle!.height / 2)
let rect = DisplayingRect(width: width, height: height, xAxis: xAxis, yAxis: yAxis)
rects.append(rect)
}
return(rects)
}
I place the rects using this code in the ContentView
ZStack{
Image(nsImage: self.img!)
.scaledToFit()
ForEach(self.rects) { rect in
Rectangle()
.fill(Color.init(.sRGB, red: 1, green: 0, blue: 0, opacity: 0.2))
.frame(width: rect.width, height: rect.height)
.offset(x: rect.xAxis, y: rect.yAxis)
}
}
If I use the original's image dimensions I get these results
But if I add
Image(nsImage: self.img!)
.resizable()
.scaledToFit()
I get these results
Is there a way to get the image dimensions and pass them and get the proper size of the image being displayed? I also need this because I can't show the whole image sometimes and need to scale it.
Thanks a lot
I would use GeometryReader on background so it reads exactly size of image, as below
#State var imageSize: CGSize = .zero // << or initial from NSImage
...
Image(nsImage: self.img!)
.resizable()
.scaledToFit()
.background(rectReader())
// ... somewhere below
private func rectReader() -> some View {
return GeometryReader { (geometry) -> Color in
let imageSize = geometry.size
DispatchQueue.main.async {
print(">> \(imageSize)") // use image actual size in your calculations
self.imageSize = imageSize
}
return .clear
}
}
Rather than pass in the frame to every view, Apple elected to give you a separate GeometryReader view that gets its frame passed in as a parameter to its child closure.
struct Example: View {
var body: some View {
GeometryReader { geometry in
Image(systemName: "check")
.onAppear {
print(geometry.frame(in: .local))
}
}
}
}