SwiftUI Catalyst App in macOS computer - Which path should I use to save an image in my Mac local disk? - save

I have this simply code, that I run with Catalyst in macOS:
struct ContentView: View {
#State private var pathURL = "/Users/cesare/Desktop/Test"
var body: some View {
VStack {
TextField("/Users/cesare/Desktop/Test", text: $pathURL)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button(action: {
let fileUrl = URL(fileURLWithPath: self.pathURL, isDirectory: true)
let nameImage = "ImageTest" + ".jpg"
let fileUrlWithName = fileUrl.appendingPathComponent(nameImage)
let imageData = UIImage(named: "2020-03-05_11-19-22_5")?.jpegData(compressionQuality: 1)
do {
try imageData!.write(to: fileUrlWithName)
} catch {
print("** saveImageData error: \(error.localizedDescription)")
}
}) {
Text("Save Image")
}
}
}
}
In Assets.xcassets I put 2020-03-05_11-19-22_5 image. When I press Save Image button, I have this error: You don’t have permission to save the file “ImageTest.jpg” in the folder “Test”.
Why I don't have permission for save image in my Desktop?
I tried to export the app contained in Products, without getting any results.
Which path should I use to save an image in my Mac local disk?

To save the document in any folder, you need to set the App Sandbox to NO.

Related

Async/Await: "out of range error" when trying to access array filled with downloaded images from firebase storage

I tried to download successfully uploaded images to storage with the code:
func retrieveAllPictures(helloid: String) async {
//Referenzen zu den Datenbanken
let db = Firestore.firestore()
//Alle Foto Ids in einem Array speichern
let result = try? await db.collection("events").document(helloid).getDocument()
allPictureIDs = (result?.data()!["pictures"] as? [String])!
var image = UIImage()
for path in allPictureIDs {
let storage = Storage.storage().reference()
let fileRef = storage.child(path)
try? await fileRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
return
} else {
image = UIImage(data: data!)!
self.retrievedEventImages.append(image)
}
}
}
}
this is how I try to access the array at index 0:
struct finalEventView: View {
#EnvironmentObject var viewModel: AppViewModel
var id: String
var body: some View {
NavigationView {
VStack{
if (viewModel.retrievedEventImages[0] != nil){
Image(uiImage: viewModel.retrievedEventImages[0]!)
}
/*
ForEach(viewModel.EventImages, id: \.self){ image in
Image(uiImage: image)
.resizable()
.frame(width: 110, height: 110)
}*/
Button(action: {
Task {
try? await viewModel.retrieveAllPictures(helloid: self.id)
}
}, label: {Text("print image arr")})
}}
.onAppear{
Task {
try? await viewModel.retrieveAllPictures(helloid: self.id)
}
}
}
}
when trying to debug the code, I see that retrievedEventImages is filled with the UIImages
still when trying to access the array at index 0 I get an out of range error
maybe someone knows how to fix it, any help is appreciated
Never access an item of an array in a SwiftUI view rendering area by index.
In most cases the view is rendered the first time while the array is empty which causes the out of range crash.
This kind of checking for the existence of an item is unswifty anyway, just use first and Optional Binding
if let firstImage = viewModel.retrievedEventImages.first {
Image(uiImage: firstImage)
}
Edit: Apparently there is no async version of getData(). You can adopt async/await with a Continuation
do {
let data : Data = try await withCheckedThrowingContinuation { continuation in
fileRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: data!)
}
}
}
let image = UIImage(data: data)!
self.retrievedEventImages.append(image)
} catch {
print(error)
// or better show a message to the user
}
See #vadian answer for an explanation of part of the issue.
To add an await/async code solution as well. I'm doing this on macOS - the iOS solution is similar.
Task {
let resultData = try! await imageRef.data(maxSize: 1 * 1024 * 1024)
let image = NSImage(data: resultData) //UIImage for iOS
self.retrievedEventImages.append(image)
}
Firebase used the await/async func data instead of getData to avoid naming collisions.
There's probably more up-to-date info but see git 8289 for further reading.

SwiftUI: Return MLKit result to SwiftUI view not working

I have a ScannerService class that has the following function:
func detectText(photo: UIImage) -> String {
let textRecognizer = TextRecognizer.textRecognizer()
let visionImage = VisionImage(image: photo)
var textRecognized = ""
visionImage.orientation = photo.imageOrientation
textRecognizer.process(visionImage) { result, error in
guard error == nil, let result = result else {
// Error handling
return
}
textRecognized = result.text
}
return textRecognized
}
I am calling this function from the ScannerView:
struct ScannerView: View {
#State var scannerService = ScannerService()
#State var detectedText = ""
var body: some View {
ZStack {
ScannerViewController(scannerService: scannerService){ result in
switch result{
case .success(let photo):
if let data = photo.fileDataRepresentation(){
let result = self.scannerService.detectText(photo: UIImage(data: data)!)
print(result)
}
else{
print("Error: No image data found")
}
case .failure(let err):
print(err.localizedDescription)
}
}
VStack {
Spacer()
Button(action: {
self.scannerService.capturePhoto()
}, label: {
Image(systemName: "circle")
.font(.system(size: 72))
.foregroundColor(.white)
})
.padding(.bottom)
}
}
}
}
Right now, I am returned an empty string rather than the result from the MLKit TextRecognizer. After some debugging, I realized this has to do with the function completing before textRecognizer.process(visionImage) finishes – since if I put a print statement in that function, it displays the correct result.
I have tried to put a DispatchQueue.main.async, however, I am still unable to get the printed result in time – however, being new to threading, I am unsure where to use it.
When I click the scan button, the result returns empty, however, when I click the scan button again I can see in the console, the previous text that was detected.
Wondering what is the best way here to let the detectText function complete so that the result can be used in the UI.
ML Kit's TextRecognizer provides both an async process(_:completion:) API and a sync results(in:) API. You can choose the one that best fits your need. Per ML Kit's API documentation, it is advised to call the sync API off the main thread to avoid blocking the UI. If you call the sync API from the main thread, you will get an NSException.

How to run a function inside a body of SWIFT UI?

i'm trying to run a function inside a body. i'm not familiar how functions work in swiftUI.
here's the code below.
struct HomeView: View {
func getDirectory() -> String{
let fm = FileManager.default
let path = Bundle.main.resourcePath!
do {
let items = try fm.contentsOfDirectory(atPath: path)
for item in items {
print("Found \(item)")
}
} catch {
// failed to read directory – bad permissions, perhaps?
}
return path
}
let documentURL = Bundle.main.url(forResource: resourceURL, withExtension: "pdf")!
var body: some View {
let path = getDirectory()
print(path)
}
}
i'm getting an error saying.
''Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type''
how can i make this work?
I can't tell what you're trying to get from this document, but to answer your question directly, you need to provide a View within the body, not a String. You can add a Text that accepts a String parameter:
var body: some View {
Text(getDirectory())
}
var body: some View {
let noView: EmptyView = {
/*
Do what you want here
*/
return EmptyView()
}()
noView
}

swift: MongoDB Realm is not syncing to mobile

when the app is opened I try to sync the cloud data to the app using this function
struct AppRootView: View {
#State var homeLink = false // <- add here
#State var loginLink = false
#State private var selection: String? = nil
var body: some View {
NavigationView { // <- wrap in the `NavigationView`
VStack(alignment: .leading) {
Text("App")
.bold()
.font(.largeTitle)
NavigationLink(destination: homeMainView(), tag: "home", selection: $selection) {EmptyView()}
NavigationLink(destination: LoginView(), tag: "login", selection: $selection) {EmptyView()}
}
}
.onAppear(perform: handleSignIn)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity).background(Color.yellow)
}
func handleSignIn() {
print("HANDLING SIGNING IN")
if let _ = app.currentUser() {
print("USER IS LOGGED IN ALREADY")
self.handleRealmSync()
self.selection = "home" // <- activate the `NavigationLink`
} else {
print("USER NEEDS TO LOGIN")
self.selection = "login"
print("not logged in; present sign in/signup view")
}
}
func handleRealmSync(){
let user = app.currentUser()
let partitionValue = "store=walmart"
Realm.asyncOpen(configuration: user!.configuration(partitionValue: partitionValue),
callback: { (maybeRealm, error) in
guard error == nil else {
fatalError("Failed to open realm: \(error!)")
}
guard let realm = maybeRealm else {
fatalError("realm is nil!")
}
// realm opened
print("Realm SYNC IS OPENED")
})
}
}
and I get the print out that "Realm SYNC IS OPENED" but I began to notice that when I query with this code
itemrealm = try! Realm(configuration: user.configuration(partitionValue: partitioningValue)
storeitems = self.itemrealm.objects(Item.self)
the query is not getting all that i have in the cloud cluster.
I guess sync was working well before until I imported more data to the cloud. I currently have atleast 10,000 data in my cluster that are given the partion value being called then I did a .count on storeitems and notice its only pulling about 4,000 items (which is the amount before I imported new data). So the cloud is not syncing the current data.
I then checked my dashboard log and saw this
a clicked on the permission error show this message
Error Type: Sync -> SyncSession End
Error:
Ending session with error: user does not have write access to partition for schema instructions (ProtocolErrorCode=206)
my sync permission is
Read : true
Write : {
"%%partition": "%%user.id"
}
Users can read all data but can only write their own data
How can I fix the permission error and sync my latest cluster to the mobile client?
when I switched the write permission to
"%%user.id": "%%partition"
this is the error i got
Fatal error: Failed to open realm: Error Domain=io.realm.unknown Code=208 "Bad client file identifier (IDENT)" UserInfo={Category=realm::sync::ProtocolError, NSLocalizedDescription=Bad client file identifier (IDENT), Error Code=208}:
Your write permissions are backwards.
instead of this (from the question)
Read : true
Write : {
"%%partition": "%%user.id"
}
do this
Read : true
Write : {
"%%user.id": "%%partition"
}
The info comes from the MongoDB Realm Sync Documentation Define Sync Rules

MessageAppExtension: how to load sticker images from assets to MSStickerBrowserView?

Alright, I know this is new for everybody but I would think it'd be a simple concept - I am following this here to make a custom sticker message app extension:
https://code.tutsplus.com/tutorials/create-an-imessage-app-in-ios-10--cms-26870
Ive copied everything exactly and am trying to create a basic MSStickerBrowserView displaying (then later filtering using logic, but haven't attempted that yet) sticker pngs I have in my assets folder here:
The tutorial did not load from assets it seems but rather just from their project, regardless their code is old as here:
var stickers = [MSSticker]()
func loadStickers() {
for i in 1...2 {
if let url = Bundle.main.urlForResource("Sticker \(i)", withExtension: "png") { //ERROR!!
do {
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "")
stickers.append(sticker)
} catch {
print(error)
}
}
}
}
I get the error
Bundle has no member URLforResource
I can't find anything on this. How can I just display my stickers programmatically in the app?
Error:
These are the images Im trying to load regardless of their name:
The reason that tutorial doesn't use asset catalogs is that you cannot get a valid fileURL for images placed in an .xcassets folder when calling the urlForResource method on the bundle.
You need to add your assets individually like you would other files you are bringing in to the app. Calling pathForResource or urlForResource on the bundle at that point will no longer return nil.
EDIT: Here is a function that will take a folder name, loop through it's contents and return [MSSticker]? based on what it finds
func createStickers(from folderName: String) -> [MSSticker]? {
guard
let path = Bundle.main.resourcePath
else { return nil }
var stickers = [MSSticker]()
let folderPath = "\(path)/\(folderName)"
let folderURL = URL(fileURLWithPath: folderPath)
//get a list of urls in the chosen directory
do {
let imageURLs = try FileManager.default.contentsOfDirectory(at: folderURL,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
//loop through the found urls
for url in imageURLs {
//create the sticker and add it, or handle error
do {
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "yourDescription")
stickers.append(sticker)
} catch let error {
print(error.localizedDescription)
}
}
} catch let error {
print(error.localizedDescription)
}
//return nil if stickers array is empty
return stickers.isEmpty ? nil : stickers
}
This should let you just call this and get what you are after:
let stickers = createStickers(from: "YourFolderName")
Please note not to include the forward slash ('/') at the beginning of the folder name.
Just replace "resourceUrl" with:
let url = Bundle.main.url(forResource: "Sticker \(i)", withExtension: "png")
The code got replaced in Swift 3.
You can put the images in a folder like so (XCODE Viewport):
It make things more organised but doesnt need as much code as if you would put them in a .xcasset.
It can be put done by creating a new group instead of creating an .xcasset by (Right Clicking Message Extension and clicking New Group):
The following code for the StickerBrowserView can be called like so:
import UIKit
import Messages
class StickerBrowserViewController: MSStickerBrowserViewController {
var stickers = [MSSticker]()
func changeBrowserViewBackgroundColor(color: UIColor){
stickerBrowserView.backgroundColor = color
}
func loadStickers(){
createSticker(asset: "1", localizedDescription:"grinning face")
createSticker(asset: "2", localizedDescription:"grimacing face")
createSticker(asset: "3", localizedDescription:"grinning face with smiling eyes")
createSticker(asset: "4", localizedDescription:"face with tears of joy")
createSticker(asset: "5", localizedDescription:"smiling face with open mouth")
createSticker(asset: "6", localizedDescription:"smiling face with open mouth and smiling eyes")
}
func createSticker(asset: String, localizedDescription: String){
guard let stickerPath = Bundle.main.path(forResource:asset, ofType:"png") else {
print("couldn't create the sticker path for", asset)
return
}
// we use URL so, it's possible to use image from network
let stickerURL = URL(fileURLWithPath:stickerPath)
let sticker: MSSticker
do {
try sticker = MSSticker(contentsOfFileURL: stickerURL, localizedDescription: localizedDescription)
// localizedDescription for accessibility
stickers.append(sticker)
}catch {
print(error)
return
}
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int{
return stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker{
return stickers[index] as MSSticker
}
}
(Ps. Not my blog, but found it on google and it has been very useful)

Resources