@DTS Engineer Thank you for your response. I'm trying to magnify a SpriteView within a SwiftUI view where the user's finger is. The SpriteView itself has a pinch gesture for zooming in, but on top of that I need to offer greater gesture precision via a magnifying glass.
MagnifyGesture + scaleEffect therefore wouldn't help.
I've also tried implementing an UIView magnifying glass within the SKScene but it just shows crosshairs.
https://github.com/niczyja/MagnifyingGlass-Swift
import UIKit
import SwiftUI
public class MagnifyingGlassView: UIView {
public weak var magnifiedView: UIView? = nil {
didSet {
removeFromSuperview()
magnifiedView?.addSubview(self)
}
}
public var magnifiedPoint: CGPoint = .zero {
didSet {
center = .init(x: magnifiedPoint.x + offset.x, y: magnifiedPoint.y + offset.y)
}
}
public var offset: CGPoint = .zero
public var radius: CGFloat = 50 {
didSet {
frame = .init(origin: frame.origin, size: .init(width: radius * 2, height: radius * 2))
layer.cornerRadius = radius
crosshair.path = crosshairPath(for: radius)
}
}
public var scale: CGFloat = 2
public var borderColor: UIColor = .lightGray {
didSet {
layer.borderColor = borderColor.cgColor
}
}
public var borderWidth: CGFloat = 3 {
didSet {
layer.borderWidth = borderWidth
}
}
public var showsCrosshair = true {
didSet {
crosshair.isHidden = !showsCrosshair
}
}
public var crosshairColor: UIColor = .lightGray {
didSet {
crosshair.strokeColor = crosshairColor.cgColor
}
}
public var crosshairWidth: CGFloat = 5 {
didSet {
crosshair.lineWidth = crosshairWidth
}
}
private let crosshair: CAShapeLayer = CAShapeLayer()
public convenience init(offset: CGPoint = .zero, radius: CGFloat = 50, scale: CGFloat = 2, borderColor: UIColor = .lightGray, borderWidth: CGFloat = 3, showsCrosshair: Bool = true, crosshairColor: UIColor = .lightGray, crosshairWidth: CGFloat = 0.5) {
self.init(frame: .zero)
layer.masksToBounds = true
layer.addSublayer(crosshair)
defer {
self.offset = offset
self.radius = radius
self.scale = scale
self.borderColor = borderColor
self.borderWidth = borderWidth
self.showsCrosshair = showsCrosshair
self.crosshairColor = crosshairColor
self.crosshairWidth = crosshairWidth
}
}
public func magnify(at point: CGPoint) {
guard magnifiedView != nil else { return }
magnifiedPoint = point
layer.setNeedsDisplay()
}
private func crosshairPath(for radius: CGFloat) -> CGPath {
let path = CGMutablePath()
path.move(to: .init(x: radius, y: 0))
path.addLine(to: .init(x: radius, y: bounds.height))
path.move(to: .init(x: 0, y: radius))
path.addLine(to: .init(x: bounds.width, y: radius))
return path
}
public override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.translateBy(x: radius, y: radius)
context.scaleBy(x: scale, y: scale)
context.translateBy(x: -magnifiedPoint.x, y: -magnifiedPoint.y)
removeFromSuperview()
magnifiedView?.layer.render(in: context)
//If above disabled, no change
//Possible that nothing's being rendered into context
//Could it be that SKScene view has no layer?
magnifiedView?.addSubview(self)
}
}
Post
Replies
Boosts
Views
Activity
iOS 18.0 (public)
Same here; all ScrollViews containing elements with DragGesture() don't scroll properly and this is causing major headaches. I've switched some from .highPriorityGesture() to .simultaneousGesture() as a stopgap measure.
The issue has been resolved.
For posterity, Xcode must have access to both wifi and bluetooth to detect the watch and make the Settings > Privacy & Security > Developer Mode list item appear. If there's no bluetooth, the list item never appears, but the Settings > Developer menu item will appear on its own despite the user never having toggled or even seen the Developer Mode list item.
This should be considered a bug, but is an edge case.
The documentation should make it clear that Bluetooth must be enabled on macOS in its "Enabling Developer Mode on a device" article.
Since Bluetooth availability was the key, tethering and other wifi-based workarounds were ineffective.
I came up with a convoluted method to sidestep this issue entirely, but this is extremely specific to my app's use-case, so I'm not going to post it here.
If anyone feels like answering this question for posterity, please feel free to do so and I'll be sure to give you your checkmark.
Always good to know what's empirically true.
The following statements in the article tripped me up:
Even better, if [MainActor.run] was already running on the main actor then the code is executed immediately – it won’t wait until the next run loop in the same way that DispatchQueue.main.async() would have done.
and
Important: If your function is already running on the main actor, using await MainActor.run() will run your code immediately without waiting for the next run loop, but using Task as shown above will wait for the next run loop.
Some more results based on variations of your code:
Task { @MainActor in
print("\(delta()) main actor will run")
...
t+0.0 start
t+1.0 main thread will sleep
t+2.1 task will start
t+2.1 task did start
t+3.0 main thread did sleep
t+3.0 main actor will run
t+3.0 main actor running
t+3.0 main actor did run
Task { @MainActor in
print("\(delta()) main actor running")
...
t+0.0 start
t+1.0 main thread will sleep
t+2.1 task will start
t+2.1 main actor will run
t+2.1 main actor did run
t+2.1 task did start
t+3.0 main thread did sleep
t+3.0 main actor running
Both waited for the run loop
@MainActor func main() async {
print("\(delta()) start")
Timer.scheduledTimer(withTimeInterval: 0.0, repeats: false) { _ in
print("\(delta()) main thread will sleep")
sleep(2)
print("\(delta()) main thread did sleep")
}
await MainActor.run {
print("\(delta()) main actor running")
}
}
t+0.1 start
t+0.1 main thread will sleep
t+2.1 main thread did sleep
t+2.1 main actor running
This shows that the statements in the article don't reflect what's empirically true. await MainActor.run() in a function that's already on the main actor will not cause the code to run immediately without waiting for the next run loop.
@TonECCT
I found an article about this:
https://www.hackingwithswift.com/quick-start/concurrency/how-to-use-mainactor-to-run-code-on-the-main-queue
The reason I failed to come across this before was that I was searching for variations of "DispatchQueue.main.async concurrency replacements" instead of looking for @MainActor directly. It does a good job going into the nuances between the two and is easily digestible.
What's important to me is that Task { @MainActor in behaves exactly like DispatchQueue.main.async because it waits for the next run-loop, which means it can be replaced 1:1 in most situations.
Using await MainActor.run { } causes the closure to run immediately, without waiting for the next run loop.
Another important difference is the Task version can be run straight from a regular context while MainActor.run needs to be in an async context.
This is perfect!
All I'll have to do is to find and replace all instances of DispatchQueue.main.async { with Task { @MainActor in in my project.
I would've never figured this out in a million years since using @MainActor inside the closure is just plain counterintuitive; doing it without some constant (e.g. list.filter { thing in thing.isTrue }) doubly so. I would've tried sacrificing gerbils when compiling before trying that.
I'll look into Observable, Combine, and async streams if the need arises, but for now getting rid of all GCD calls and making my app pure async-await is enough.
Thank you very much
Thanks for the prompt response!
I managed to solve the issue after doing all the things I've avoided doing out of laziness and drilling deep into my code, and then realizing that my implementation of the animation was either the root cause or a major contributor
Uninteresting post-mortem
I excised all state for the loading screen into its own view model, granting me more control over its concurrency.
I then designated the animation function @MainActor and the workload nonisolated (I'm a terrible person)
These failed to solve the issue, so after jiggling a lot of knobs I drilled deep into the workload code
This yielded a function that ran on @MainActor deep into the code, so I fixed that and the issue still wasn't fixed.
More knobs were jiggled, no help
Realized the animation itself was poorly implemented since I wrote that when I was very new to coding
Long story short There's a beachball-like image that rotates 360 degrees during loading, but back then I didn't know about .repeatForever(), so instead I jury rigged an abomination:
Task {
while loading {
try await Task.sleep(for: .seconds(0.5))
withAnimation(.linear(duration: 0.5)) {
beachballRotation += 90
}
}
}
The issues went away after replacing all that with:
withAnimation(.linear(duration: 2).repeatForever(autoreverses: false)) {
beachballRotation = 360
}
The animation still jumps at the 360 degree mark, when the animation repeats itself. Other than that, everything works smoothly, so I'm sure I'll eventually figure something out.
I bet moving everything to a view model and setting the workload to nonisolated also helped (again, I'm a terrible person and I'm sorry). My guess is timing imperfections + frequent new calls during heavy load processing lead to the sputtering and freezing.
But all of this has reminded me of a question I've always had:
In the process of moving to nonisolated, I've had to disfigure my code by inserting DispatchQueue.main.async closures. This is my go-to solution whenever a purple or yellow warning pops up telling me some async code should be on the main thread. But, every time I do this, there's a little voice at the back of my head reminding me of a warning a younger me received about never crossing the streams. i.e. One should never mix the antiquated DispatchQueue.main.async/GCD with the modern async-await.
I'm a SwiftUI native, and all my concurrency is technically async-await. At last count I have 150 instances where I've crossed the streams. Many of these were done out of desperation because my app makes heavy use of SpriteKit, whose SKScene demands all calls to be on the main queue. Everything seems to work fine for now, but this is still something I'd like to fix before it blows up in my face or Apple finally makes it a bona-fide error.
tl;dr - How does one avoid using DispatchQueue.main.async to encapsulate async-await code that should be on main?
@DTS Engineer if you feel this deserves its own thread, please let me know and I'll make one.
@mikestern Thanks to you, I managed to dodge a bullet. For posterity, I'll post a short write-up of my findings (Jul. 10, 2024).
Long story short, I assumed that fonts included in macOS are all free to use in apps for the Apple ecosystem, but your answer prompted me to double check that assumption.
Warning: I am not a laywer, and this is not legal advice
Usage of the fonts included in macOS' FontBook are governed by the macOS Sonoma Licensing Agreement, then by the embedding restrictions and licenses set forth by the actual creators of the font.
On page 3, under Section 1:
E. Fonts. Subject to the terms and conditions of this License, you may use the fonts included with the Apple Software to display and print content while running the Apple Software; however, you may only embed fonts in content if that is permitted by the embedding restrictions accompanying the font in question. These embedding restrictions can be found in the Font Book/Preview/Show Font Info panel.
The word "embedding" is key here. In similar contexts elsewhere, the word implies non-interactive uses like in posters or a document, and typically excludes apps. It would be safer to assume this also holds true here. This means one should instead look to the font's licensing information, which can also be found by opening FontBook and opening the info panel (Cmd + I) and looking under "Usage".
Many of the fonts have restrictive licenses, and among these are the SF family of fonts. These fonts, despite being the default font used in iOS app development, cannot be included as individual fonts in the app. It would be mind-bogglingly counterproductive for Apple to place the system font under a license that disallows developers from including it in their apps, so I can safely assume that one variant can be used via the system font, just as you said
Absent actual legal advice, I would play it safe and avoid all FontBook fonts except those with a license explicitly allowing commercial use, such as the SIL Open Font License. A safer repository of fonts would be Google Fonts, where the vast majority of fonts are under SIL or Apache.
I managed to get JXL working after a lot of angst and grit. I didn't have C++ compilation experience but I managed to get the job done, so this might be encouragement for anyone stuck in a similar situation (no guarantees though).
Anyone interested in JXL for iOS should be able to replicate what I did via this thread:
https://developer.apple.com/forums/thread/757359
wmk's Misadventures in Compiling C++, Finale (?)
JXL is working on my iPhone after I got rid of libjpegli. The weeklong tumble down the rabbit hole of C++ compilation is finally over; and thus ends this impromptu blog I started writing for debugging purposes and to keep myself sane.
At the end of this I look back and realize how much I've been spoiled by Xcode, where compiling is just a mindless habit. Having to figure out CMake after being raised on Xcode is like getting chucked into the slums after having lived all your life in an ivory tower being pampered by servants who aren't perfect, but get the job done.
I've done some preliminary tests on encoding, decoding, and animation and everything seems to be working fine. The code in https://github.com/awxkee/jxl-coder-swift works even with TaskGroups, which allows me to turbocharge everything.
JXL so far seems to be absolutely worth the effort.
Now I just need to fiddle with the parameters to optimize performance for my app, and actually examine the animation output.
@endecotp I cannot thank you enough for introducing me to JXL and sticking with me until I got it working. I was about to spend perhaps hundreds of dollars, but in the end all I spent was $2.99 on an Upwork job post. If the opportunity ever arises, I'd definitely return the favor.
P.S. Upwork is a dumpster fire.
That's probably it, given how touchy CMake seems to be
I'm currently stuck because I can't get jpegli to compile, and apparently its wired into the package. It just absolutely refuses to be compiled. I suspect it has something to do with
-- Could NOT find JPEG (missing: JPEG_LIBRARY JPEG_INCLUDE_DIR)
But I don't know where to begin because, while installing libjpeg-turbo on this would probably lead to the warning going away, it would likely lead to cross-compiling issues (that thing Quinn was talking about).
Apparently I'm not the only one with issues compiling jpegli: https://github.com/libjxl/libjxl/issues/3440
I've also tried a slew of different hacks, including isolating it and forcing it to compile, but I'm stuck on this error:
CMake Error at jpegli.cmake:46 (target_link_libraries):
Target "jpegli-static" links to:
Threads::Threads
but the target was not found. Possible reasons include:
* There is a typo in the target name.
* A find_package call is missing for an IMPORTED target.
* An ALIAS target is missing.
Anyways, I'll figure something out, like precisely excising it from libjxl
wmk's Misadventures in Compiling C++, Episode 2
After a break, it became apparent that tools had to be purged from the process entirely. This was accomplished by removing add_subdirectory(tools) from the main CMakeLists.txt.
[ 73%] Linking CXX executable tests/codec_test.app/tests/codec_test
ld: open() failed, errno=2 for 'tests/codec_test.app/tests/codec_test'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [lib/tests/codec_test.app/tests/codec_test] Error 1
make[1]: *** [lib/CMakeFiles/codec_test.dir/all] Error 2
make: *** [all] Error 2
I'm finally back to where I started--question now is why this is being built at all.
Since I made progress by removing a part of the main CMakeLists.txt, I decided to go all-in and started hacking away entire sections of the file like I was chopping a piece of wood. So this is why they call it "hacking". My mental image of a hacker became a bit more like a lumberjack.
Everything below if (JPEGXL_ENABLE_MANPAGES) was hacked off. This means no testing, but at this point I was willing to accept this tradeoff just to see some progress.
The result was counterintuitive: we're back to ld: unknown options: --exclude-libs=ALL at a dylib. It was here I realized just how finnicky CMake is and restarted my mac.
Starting with a fresh ios-cmake and libjxl, I went through all the steps again, removing the bottom chunk of CMakeLists.txt.
Success!
[ 95%] Built target jxl_enc-obj
[ 95%] Linking CXX static library libjxl_dec-internal.a
[ 95%] Built target jxl_dec-internal
[ 96%] Linking CXX static library libjxl-internal.a
[ 96%] Built target jxl-internal
[ 96%] Linking CXX static library libjxl.a
[ 96%] Built target jxl
[ 97%] Linking CXX static library libjxl_dec.a
[ 97%] Built target jxl_dec
[ 98%] Building CXX object lib/CMakeFiles/jxl_threads.dir/threads/resizable_parallel_runner.cc.o
[ 98%] Building CXX object lib/CMakeFiles/jxl_threads.dir/threads/thread_parallel_runner.cc.o
[100%] Building CXX object lib/CMakeFiles/jxl_threads.dir/threads/thread_parallel_runner_internal.cc.o
[100%] Linking CXX static library libjxl_threads.a
[100%] Built target jxl_threads
Now to see whether this is a stillbirth, an abomination, or a little angel that poops out JXL. My expectations are low; what I've done so far is trivial, especially for a professional, and we do have a professional working at TikTok's parent company saying, "This coder does not supports JPEG-XL encoding (Because I have no time :))",.
The adventure continues...
Just knowing something is out of my league makes things so much better. At least I won't be pestering people for help while thinking it's a quick and simple task.
Thank you so very much.
In the interim I had already opened an account on Upwork, waded through LLM spam and grifters, found someone willing to do it for $150, had money persistently rejected by Upwork, found its support intentionally disabled, then closed my account (takeaway: Upwork is a dumpster fire). That was 6 hours down the drain.
In comparison, this is a godsend, even if I might need to put in some work myself
wmk's Misadventures in Compiling C++, Episode 1
I used to get around the install TARGETS given no BUNDLE DESTINATION error by adding BUNDLE DESTINATION after RUNTIME DESTINATION but I had to do it for tools/CMakeLists.txt as well.
I cleared everything and followed your instructions from scratch, verbatim.
My first attempt after git cloning and modifying the brotli/CMakeLists.txt, I was getting the error:
CMake Error at tools/CMakeLists.txt:340 (install):
install TARGETS given no BUNDLE DESTINATION for MACOSX_BUNDLE executable
target "cjxl".
Maybe your invocation was supposed to disable the building of tools, so I double-checked that mine's a verbatim copy of yours (it was) and considered whether your libjxl might be older. Since it seems tools was still being built for some reason, I appended the following to your command:
-DJPEGXL_ENABLE_DEVTOOLS=false \
-DJPEGXL_ENABLE_VIEWERS=false \
-DJPEGXL_ENABLE_PLUGINS=false \
-DJPEGXL_TEST_TOOLS=false \
-DJPEGXL_STATIC=true
Same error. So I applied your fix by removing the offending install call from tools/CMakeLists.txt and reverted to your original command. The config succeeds but always has zsh: no such file or directory: -DCMAKE_INSTALL_PREFIX=/Users/username/Desktop/compiled at the end (Note: I clear build.ios between every attempt).
I then ran make:
[ 61%] Linking CXX executable tests/codec_test.app/tests/codec_test
ld: open() failed, errno=2 for 'tests/codec_test.app/tests/codec_test'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [lib/tests/codec_test.app/tests/codec_test] Error 1
make[1]: *** [lib/CMakeFiles/codec_test.dir/all] Error 2
make: *** [all] Error 2
Little did I know, I'll be yearning to see this error in an hour.
I saw "executable" and "test" and realized it was still compiling executables for tests, so I went back and added the extra flags above, then ran make again.
[ 20%] Linking CXX shared library libjxl_dec.dylib
ld: unknown options: --exclude-libs=ALL
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [lib/libjxl_dec.0.10.2.dylib] Error 1
make[1]: *** [lib/CMakeFiles/jxl_dec.dir/all] Error 2
make: *** [all] Error 2
Why is it building a dylib? Is there an issue with the flags? I tried variations of the invocation
your flags + -DJPEGXL_STATIC=true:
Same error
I decided to give your vanilla command another try:
Same error?
Clearly some state is being saved somewhere, so I wiped the entire libjxl folder and copied in the untouched original, then reapplied CMakeLists.txt changes:
Still the same error!?
This was where grand attempt 1 was reset since I was stuck with the dylib error (I'm writing this log as I retrace my steps with grand attempt 2)
I tried various changes, including retracing my precise steps above, but I couldn't return to the previous error. I suspected CMake has some sort of cache/state set somewhere, so I looked online, found nothing, then did what any good techie did and reset my mac. Still there.
Grand attempt 3 was basically the same except I never saw ld: open() failed, errno=2 .
env: macOS 14.5, CMake 3.29.6
The adventure continues...