AVMetaDataObject.bounds to SwiftUI position? - swift

I have two views. The parent view holds a CameraFeed UIViewControllerRepresentable which passes back the bounds of a AVMetadataFaceObject. I'm trying to draw overlays where that bounds should be. I'm getting kind of close, but my mappings aren't quite right. The CameraFeed passes back a scalar-style set of bounds and I'm multiplying my geometry reader by it.
The X and the Y seem to be swapped? (orange square works wrong and blue square works better)
The location doesn't QUITE line up perfectly.
Acknowledgements: Thanks to the HackingWithSwift InstaFilter and Hot Prospects examples, btw. They were very helpful to getting this far.
I do understand that there is a CALayer that I could draw on in the AV object, and if I went that way I could use the more powerful Vision framework but I was seeing if I could try this approach first. I also am aware that there is a CIFaceFeature I could be using too. Also, I don't have a TrueDepth front-facing camera to work with. I just wanted to see if I could hijack this seemingly simplest of solutions to make it work.
What am I missing about the how how the bounds of AVMetaDataObject work and approaches to doing the frame of reference transformation? Thanks in advance. Full Code on GitHub
struct CameraFeedView: View {
#State var foundFace:CGRect?
#State var geometryRect:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
//#State var foundFaceAdjusted:CGRect?
//var testRect:CGRect = CGRect(
// x: 0.44112000039964916,
// y: 0.1979580322805941,
// width: 0.3337599992007017,
// height: 0.5941303606941507)
var body: some View {
GeometryReader(content: { geometry in
ZStack {
CameraFeed(codeTypes: [.face], completion: handleCameraReturn)
if (foundFace != nil) {
Rectangle()
.stroke()
.foregroundColor(.orange)
.frame(width: 100, height: 100, alignment: .topLeading)
.position(
x: geometry.size.width * foundFace!.origin.x,
y: geometry.size.height * foundFace!.origin.y)
FoundObject(frameRect: geometryRect, boundsRect: foundFace!)
.stroke()
.foregroundColor(.blue)
}
}
.onAppear(perform: {
let frame = geometry.frame(in: .global)
geometryRect = CGRect(
origin: CGPoint(x: frame.minX, y: frame.minY),
size: geometry.size
)
})
})
}
func handleCameraReturn(result: Result<CGRect, CameraFeed.CameraError>) {
switch result {
case .success(let bounds):
print(bounds)
foundFace = bounds
//TODO: Add a timer
case .failure(let error):
print("Scanning failed: \(error)")
foundFace = nil
}
}
}
struct FoundObject: Shape {
func reMapBoundries(frameRect:CGRect, boundsRect:CGRect) -> CGRect {
//Y bounded to width? Really?
let newY = (frameRect.width * boundsRect.origin.x) + (1.0-frameRect.origin.x)
//X bounded to height? Really?
let newX = (frameRect.height * boundsRect.origin.y) + (1.0-frameRect.origin.y)
let newWidth = 100//(frameRect.width * boundsRect.width)
let newHeight = 100//(frameRect.height * boundsRect.height)
let newRect = CGRect(
origin: CGPoint(x: newX, y: newY),
size: CGSize(width: newWidth, height: newHeight))
return newRect
}
let frameRect:CGRect
let boundsRect:CGRect
func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(reMapBoundries(frameRect: frameRect, boundsRect: boundsRect))
return path
}
}

Related

Is SKCropNode limited to a width below 5000?

I am trying to use a SKCropNode with a width of at least 5000 pixels, yet if the maskNode property width is greater than 4100 something strange happens -- the first 100-200 or so pixels are not cropped, and the cropping spans approx only 4100 pixels.
I'm on a Mac Mini M1, Ventura 13.0.1 and Xcode 14.1 (14B47b)
Here is the working code:
import SwiftUI
import SpriteKit
struct ContentView: View {
#State var spriteView : SpriteView!
var body: some View {
ScrollView(.horizontal, showsIndicators: true) {
ZStack {
if spriteView != nil {
spriteView
}
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
}
.padding()
.onAppear {
spriteView = .init(scene: myScene)
}
.frame(minWidth: 1000, minHeight: 500)
}
}
var myScene : SKScene {
let r = SKScene.init(size: .init(width: 2000, height: 500))
r.backgroundColor = .yellow
let maskRect = SKShapeNode.init(rect: .init(x: 0, y: 100, width: 5000, height: 300))
// let maskRect = SKShapeNode.init(rect: .init(x: 0, y: 100, width: 4096+2, height: 300))
maskRect.fillColor = .white
maskRect.lineWidth = 0
let redRect = SKShapeNode.init(rect: .init(origin: .zero, size: .init(width: 5000, height: 500)))
redRect.fillColor = .red
redRect.lineWidth = 0
let path = CGMutablePath.init()
path.move(to: .zero)
path.addLine(to: .init(x: 1000, y: 500))
let blueLine = SKShapeNode.init(path: path)
blueLine.strokeColor = .blue
blueLine.lineWidth = 12
redRect.addChild(blueLine)
let cropNode = SKCropNode.init()
cropNode.maskNode = SKNode.init()
cropNode.maskNode?.addChild(maskRect)
cropNode.addChild(redRect)
let blackRect = SKShapeNode.init(rect: .init(origin: .init(x: 0, y: 100), size: .init(width: 5000, height: 300)))
blackRect.fillColor = .black
blackRect.lineWidth = 0
r.addChild(blackRect)
r.addChild(cropNode)
return r
}
}
The result I get is
If I change the width to 4096 I get the correct result without the black rectangle on the left hand side, ie let maskRect = SKShapeNode.init(rect: .init(x: 0, y: 100, width: 4096, height: 300))
EDIT:
Using SKSpriteNode as suggested produced the same results for me:
let maskRect = SKSpriteNode.init(color: .black, size: .init(width: 5000, height: 300))
maskRect.position.x = 0 + maskRect.size.width/2
maskRect.position.y = 100 + maskRect.size.height/2
this appears to be an issue with SKShapeNode.
solution: use SKSpriteNode instead -- both for your mask and for any other large elements in the scene. here is an illustration (swap the red and green nodes to see the glitch):
import SwiftUI
import SpriteKit
struct GameSceneCrop: View {
#State private var scene = CropScene()
var body: some View {
SpriteView(scene: scene)
}
}
class CropScene: SKScene {
let skCameraNode = SKCameraNode()
var WIDTH:CGFloat = 5000
var HEIGHT:CGFloat = 1000
let container = SKNode()
override func didMove(to view: SKView) {
scaleMode = .aspectFill // Set the scale mode to scale to fit the window
backgroundColor = .yellow
anchorPoint = CGPoint(x: 0.5, y: 0.5)
//set up camera node
camera = skCameraNode
addChild(skCameraNode)
skCameraNode.setScale( 10000 )
addChild(container)
rebuild()
}
func rebuild() {
container.removeAllChildren()
//a green SKSpriteNode
let green = SKSpriteNode(color: .green, size: CGSize(width: WIDTH, height: HEIGHT))
//a red SKShapeNode
let red = SKShapeNode(rectOf: CGSize(width: WIDTH, height: HEIGHT))
red.fillColor = .red
//crop node
let mask = SKSpriteNode(color: .black, size: red.frame.size) //using SKSpriteNode here instead of SKShapeNode
let crop_node = SKCropNode()
crop_node.maskNode = mask
crop_node.addChild(green) //<--- replace green SKSpriteNode with red SKShapeNode to see clipping glitch
container.addChild(crop_node)
//draw outline around red block
let reference_frame = SKShapeNode(rect: red.frame.insetBy(dx: -50, dy: -50))
reference_frame.strokeColor = .black
reference_frame.lineWidth = 50
container.addChild(reference_frame)
}
override func update(_ currentTime: TimeInterval) {
let x = 0.65 + (sin(currentTime*2) + 1) / 4
WIDTH = 5000 * x
rebuild()
}
}

How to implement the same iOS 16 lock screen circular widget myself?

I'm trying to implement lock screen widget myself
Widget I currently implement
I want to implement this ios16 lock screen widget
I've made almost everything, but I haven't been able to implement the small circle's transparent border.
I couldn't find a way to make even the background of the ring behind it transparent.
My code
struct RingTipShape: Shape { // small circle
var currentPercentage: Double
var thickness: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
let angle = CGFloat((240 * currentPercentage) * .pi / 180)
let controlRadius: CGFloat = rect.width / 2 - thickness / 2
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
let x = center.x + controlRadius * cos(angle)
let y = center.y + controlRadius * sin(angle)
let pointCenter = CGPoint(x: x, y: y)
path.addEllipse(in:
CGRect(
x: pointCenter.x - thickness / 2,
y: pointCenter.y - thickness / 2,
width: thickness,
height: thickness
)
)
return path
}
var animatableData: Double {
get { return currentPercentage }
set { currentPercentage = newValue }
}
}
struct RingShape: Shape {
var currentPercentage: Double
var thickness: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.width / 2, y: rect.height / 2), radius: rect.width / 2 - (thickness / 2), startAngle: Angle(degrees: 0), endAngle: Angle(degrees: currentPercentage * 240), clockwise: false)
return path.strokedPath(.init(lineWidth: thickness, lineCap: .round, lineJoin: .round))
}
var animatableData: Double {
get { return currentPercentage}
set { currentPercentage = newValue}
}
}
struct CircularWidgetView: View { // My customizing widget view
#State var percentage: Double = 1.0
var body: some View {
GeometryReader { geo in
ZStack {
RingBackgroundShape(thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.white.opacity(0.21))
RingShape(currentPercentage: 0.5, thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.white.opacity(0.385))
RingTipShape(currentPercentage: 0.5, thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.white)
/*
I want to make RingTipShape completely
transparent. Ignoring even the RingShape behind it
*/
VStack(spacing: 4) {
Image(systemName: "scooter")
.resizable()
.frame(width: 24, height: 24)
Text("hello")
.font(.system(size: 10, weight: .semibold))
.lineLimit(1)
.minimumScaleFactor(0.1)
}
}
}
}
}
How can I make a transparent border that also ignores the background of the view behind it?
This is a great exercise. The missing piece is a mask.
Note: Despite the fact that there are numerous ways to improve the existing code, I will try to stick to the original solution since the point is to gain experience through practice (based on the comments). However I will share some tips at the end.
So we can think of it in two steps:
We need some way to make another RingTipShape at the same (centered) position as our existing but a bit larger.
We need to find a way to create a mask that removes only that shape from other content (in our case the track rings)
The first point is an easy one, we just need to define the outer thickness in order to place the ellipse on top of the track at the correct location:
struct RingTipShape: Shape { // small circle
//...
let outerThickness: CGFloat
//...
let controlRadius: CGFloat = rect.width / 2 - outerThickness / 2
//...
}
then our existing code changes to:
RingTipShape(currentPercentage: percentage, thickness: 5.5, outerThickness: 5.5)
now for the second part we need something to create a larger circle, which is easy:
RingTipShape(currentPercentage: percentage, thickness: 10.0, outerThickness: 5.5)
ok so now for the final part, we are going to use this (larger) shape to create a kind of inverted mask:
private var thumbMask: some View {
ZStack {
Color.white // This part will be transparent
RingTipShape(currentPercentage: percentage, thickness: 10.0, outerThickness: 5.5)
.fill(Color.black) // This will be masked out
.rotationEffect(Angle(degrees: 150))
}
.compositingGroup() // Rasterize the shape
.luminanceToAlpha() // Map luminance to alpha values
}
and we apply the mask like this:
RingShape(currentPercentage: percentage, thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.foregroundColor(.white.opacity(0.385))
.mask(thumbMask)
which results to this:
Some observations/tips:
You don't need the GeometryReader (and all the frame modifiers) in your CircularWidgetView, the ZStack will offer all available space to views.
You can add .aspectRatio(contentMode: .fit) to your image in order to avoid stretching.
You could take advantage of existing apis for making your track shapes.
For example:
struct MyGauge: View {
let value: Double = 0.5
let range = 0.1...0.9
var body: some View {
ZStack {
// Backing track
track().opacity(0.2)
// Value track
track(showsProgress: true)
}
}
private var mappedValue: Double {
(range.upperBound + range.lowerBound) * value
}
private func track(showsProgress: Bool = false) -> some View {
Circle()
.trim(from: range.lowerBound, to: showsProgress ? mappedValue : range.upperBound)
.stroke(.white, style: .init(lineWidth: 5.5, lineCap: .round))
.rotationEffect(.radians(Double.pi / 2))
}
}
would result to:
which simplifies things a bit by utilizing the trim modifier.
I hope that this makes sense.

How to set offset on last element in dynamic scrollable Path in SwiftUI

i'm trying to construct a view with continuous path as graph in scrollView.
I'm receiving data from BLE device and transform them into a vector based on timestamps. While vector is growing in time in width i'm tying to follow it in scrollView. I tired implementation with ScrollViewReader but with no luck, becouse if i understood well i should have id of element i want to scroll to. It's not possible as i know becouse whole signal is one path drawing at once.
I tried to set offset of uiScrollView based on last element width but i can't get scrollView growing as Path with chart is...
I used implementation of uiScrollView wraper: https://gist.github.com/jfuellert/67e91df63394d7c9b713419ed8e2beb7
and in it i'm drawing a path based on values and constant value as xStep
struct LineChart: Shape {
let geoProxy: GeometryProxy
#Binding var lead: ECGLead
#Binding var timestamps: [Int]
var n: vDSP_Length{
return vDSP_Length(lead.channelVector.count)
}
let stride = vDSP_Stride(1)
var xPosition: Binding<CGFloat>
func path(in rect: CGRect) -> Path {
Path { path in
let xStep = 0.00015 * geoProxy.size.width
var currentX: CGFloat = xStep
let signal = transformIntoSignal(lead: lead , timestamps: timestamps)
path.move(to: .zero)
//if signal.count
signal.forEach{ signalValue in
//let yStep = CGFloat($0) * geoProxy.frame(in: .local).height
//print("X: \(currentX) Y: \($0)")
path.addLine(to: CGPoint(x: currentX, y: CGFloat(signalValue)))
currentX += xStep
print("Last x: \(currentX)")
//print("Checking xStep = \(currentX)")
}
}
}
func transformIntoSignal(lead: ECGLead, timestamps: [Int]) -> [Float] {
var outputSignal = [Float](repeating: 0,
count: Int(n))
let time = timestamps.map{Float($0)}
vDSP_vgenp(lead.channelVector, stride,
time, stride,
&outputSignal, stride,
vDSP_Length(lead.channelVector.count),
vDSP_Length(timestamps.count))
return outputSignal
}
}
With that path i'm trying to give it's maxWidth = .inifity to be able to grow in scrollView
ZStack(){
RoundedRectangle(cornerRadius: 8)
.frame(minWidth: 120, idealWidth: 170, maxWidth: .infinity, minHeight: 60, idealHeight: 90, maxHeight: 90, alignment: .center)
.foregroundColor(.white)
.overlay(
ScrollableView($lineOffset, animationDuration: TimeInterval(1), content: {
//HStack{
LineChart(vector: animatableVector, geoProxy: geoProxy,lead: $ecgLead,timestamps: $timeStamps, xPosition: $lastPointX)
.stroke(appGreen, lineWidth: 1)
.frame(minWidth: 120, idealWidth: 170, maxWidth: .infinity, minHeight: 35, idealHeight: 40, maxHeight: 50,alignment: Alignment(horizontal: .leading, vertical: .center))
})//.frame(minWidth: 120, idealWidth: 170, maxWidth: .infinity, minHeight: 35, idealHeight: 50, maxHeight: 60,alignment: Alignment(horizontal: .leading, vertical: .center))
)
With uiScrollView wrapper i tried to programmatically scroll to content offset but each time i saw jumping view around some middle value and end continuously.
Here is update method from UIkit wraper mentioned above:
func updateUIViewController(_ viewController: UIViewControllerType, context: UIViewControllerRepresentableContext<Self>) {
viewController.scrollView.showsVerticalScrollIndicator = self.showsScrollIndicator
viewController.scrollView.showsHorizontalScrollIndicator = self.showsScrollIndicator
viewController.updateContent(self.content)
viewController.scrollView.invalidateIntrinsicContentSize()
let lastPoint = viewController.scrollView.contentSize.width
let lastPointOffset = viewController.scrollView.contentOffset.x
let tmp = viewController.scrollView.visibleSize.width
let contentWidth = viewController.scrollView.bounds.width
print("Check last point: \(lastPoint)")
print("Check last point offset: \(lastPointOffset)")
print("Check tmp: \(contentWidth)")
viewController.scrollView.setContentOffset(CGPoint(x: lastPoint, y: 0), animated: true)
viewController.scrollView.bounds.width
let duration: TimeInterval = self.duration(viewController)
let newValue: CGPoint = self.offset.wrappedValue
viewController.scrollView.isScrollEnabled = !self.disableScroll
if self.stopScrolling.wrappedValue {
viewController.scrollView.setContentOffset(viewController.scrollView.contentOffset, animated:true)
return
}
guard duration != .zero else {
viewController.scrollView.contentOffset = newValue
return
}
UIView.animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveEaseInOut, .beginFromCurrentState], animations: {
viewController.scrollView.contentOffset = newValue
}, completion: nil)
}
My question is how to make scroll always focused on last element of growing array of this signal in scrollView. Is it not working becouse somewhere is width not growing correctly? Or i misunderstood somewhere? For now i don't have an idea where to continue
Thank you in advance for answering my question. Any help will by really appreciated :)

How to Create a SwiftUI RoundedStar Shape?

This is a self-answered question which is perfectly acceptable (and even encouraged) on Stack Overflow. The point is to share something useful to others.
SwiftUI has a RoundedRectangle Shape. It would be nice to have a five-pointed star with rounded tips that could be used for filling, clipping, and animation.
This Stack Overflow answer shows how to make a RoundedStar as a custom UIView using UIBezierPath.
How can this code be adapted to SwiftUI as a Shape that can be animated?
Here is the RoundedStar code adapted as an animatable SwiftUI Shape:
// Five-point star with rounded tips
struct RoundedStar: Shape {
var cornerRadius: CGFloat
var animatableData: CGFloat {
get { return cornerRadius }
set { cornerRadius = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
let r = rect.width / 2
let rc = cornerRadius
let rn = r * 0.95 - rc
// start angle at -18 degrees so that it points up
var cangle = -18.0
for i in 1 ... 5 {
// compute center point of tip arc
let cc = CGPoint(x: center.x + rn * CGFloat(cos(Angle(degrees: cangle).radians)), y: center.y + rn * CGFloat(sin(Angle(degrees: cangle).radians)))
// compute tangent point along tip arc
let p = CGPoint(x: cc.x + rc * CGFloat(cos(Angle(degrees: cangle - 72).radians)), y: cc.y + rc * CGFloat(sin(Angle(degrees: (cangle - 72)).radians)))
if i == 1 {
path.move(to: p)
} else {
path.addLine(to: p)
}
// add 144 degree arc to draw the corner
path.addArc(center: cc, radius: rc, startAngle: Angle(degrees: cangle - 72), endAngle: Angle(degrees: cangle + 72), clockwise: false)
// Move 144 degrees to the next point in the star
cangle += 144
}
return path
}
}
The code is very similar to the UIBezierPath version except that it uses the new Angle type which provides easy access to both degrees and radians. The code to draw the star rotated was removed because it is easy to add rotation to a SwiftUI shape with the .rotationEffect(angle:) view modifier.
Demonstration:
Here is a demonstration that show the animatable qualities of the cornerRadius setting as well as showing what the various cornerRadius settings look like on a full-screen star.
struct ContentView: View {
#State private var radius: CGFloat = 0.0
var body: some View {
ZStack {
Color.blue.edgesIgnoringSafeArea(.all)
VStack(spacing: 40) {
Spacer()
RoundedStar(cornerRadius: radius)
.aspectRatio(1, contentMode: .fit)
.foregroundColor(.yellow)
.overlay(Text(" cornerRadius: \(Int(self.radius)) ").font(.body))
HStack {
ForEach([0, 10, 20, 40, 80, 200], id: \.self) { value in
Button(String(value)) {
withAnimation(.easeInOut(duration: 0.3)) {
self.radius = CGFloat(value)
}
}
.frame(width: 50, height: 50)
.foregroundColor(.black)
.background(Color.yellow.cornerRadius(8))
}
}
Spacer()
}
}
}
}
Running in Swift Playgrounds on iPad
This runs beautifully on an iPad in the Swift Playgrounds app. Just add:
import PlaygroundSupport
at the top and
PlaygroundPage.current.setLiveView(ContentView())
at the end.
Using the RoundedStar shape to create EU Flag
struct ContentView: View {
static let flagSize: CGFloat = 234 // Change this to resize flag
let flagHeight: CGFloat = flagSize
let flagWidth: CGFloat = flagSize * 1.5
let radius: CGFloat = flagSize / 3
let starWidth: CGFloat = flagSize / 9
let pantoneReflexBlue = UIColor(red: 0, green: 0x33/0xff, blue: 0x99/0xff, alpha: 1)
let pantoneYellow = UIColor(red: 1, green: 0xcc/0xff, blue: 0, alpha: 1)
var body: some View {
ZStack {
Color(pantoneReflexBlue).frame(width: flagWidth, height: flagHeight, alignment: .center)
ForEach(0..<12) { n in
RoundedStar(cornerRadius: 0)
.frame(width: starWidth, height: starWidth)
.offset(x: radius * cos(CGFloat(n) / CGFloat(12) * 2 * .pi), y: radius * sin(CGFloat(n) / CGFloat(12) * 2 * .pi))
.foregroundColor(Color(pantoneYellow))
}
}
}
}

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))
}
}
}
}