I'm trying to filter results to get all characters that are associated with a story. When navigating to the view it filters as it should, but as soon as you select the Add Character option, the app just freezes.
I've found that if I comment out the Query line in the init, and filter out as part of the foreach I don't get the freeze.
Can anyone advise how I should be doing these predicates as it seems init isn't the best place to do it
The views code is
import SwiftUI
import SwiftData
struct CharacterListView: View {
let story : Story
@Query(sort: \Character.name, order: .forward) private var characters: [Character]
var body: some View {
List {
ForEach(characters) { char in
CharacterListCellView(char: char)
}
}
.toolbar {
ToolbarItem {
NavigationLink(destination: CharacterAddView(story: story)) {
Label("Add Character", systemImage: "plus")
}
}
}
.navigationBarTitle("Characters", displayMode: .inline)
}
init(story: Story) {
self.story = story
let id = story.persistentModelID
let predicate = #Predicate<Character> { char in
char.story?.persistentModelID == id
}
_characters = Query(filter: predicate, sort: [SortDescriptor(\.name)] )
}
}
I've narrowed it down to a line in the AddCharacterView file
struct CharacterAddView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) var dismiss
let story : Story
// Character details
@State private var characterName: String = ""
// Images
@State private var showImageMenu = false;
@State private var isShowPhotoLibrary = false
@State private var isShowCamera = false
//@State private var image = UIImage() <-- This line
@State private var imageChanged = false
@State private var isSaving : Bool = false
var body: some View {
}
}
If I do either of the below it works
If I comment out the UIImage variable then I don't get the freeze, but I can't update the image variable
If I comment out the query in init, then it works but doesn't filter and I have to do it as part of the foreach
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
I have a project that currently has data saved locally and I'm trying to get it to sync over multiple devices.
Currently basic data is syncing perfectly fine, but I'm having issues getting the images to convert to data. From what I've researched it because I'm using a UIImage to convert and this caches the image
It works fine when there's only a few images, but if there's several its a pain
The associated code
func updateLocalImages() {
autoreleasepool {
let fetchRequest: NSFetchRequest<Project> = Project.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "converted = %d", false)
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Project.statusOrder?.sortOrder, ascending: true), NSSortDescriptor(keyPath: \Project.name, ascending: true)]
do {
let projects = try viewContext.fetch(fetchRequest)
for project in projects {
currentPicNumber = 0
currentProjectName = project.name ?? "Error loading project"
if let pictures = project.pictures {
projectPicNumber = pictures.count
for pic in pictures {
currentPicNumber = currentPicNumber + 1
let picture : Picture = pic as! Picture
if let imgData = convertImage(picture: picture) {
picture.pictureData = imgData
}
}
project.converted = true
saveContext()
}
}
} catch {
print("Fetch Failed")
}
}
}
func convertImage(picture : Picture)-> Data? {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let path = paths[0]
if let picName = picture.pictureName {
let imagePath = path.appendingPathComponent(picName)
if let uiImage = UIImage(contentsOfFile: imagePath.path) {
if let imageData = uiImage.jpegData(compressionQuality: 0.5) {
return imageData
}
}
}
return nil
}```
I'm trying to look at what the best way to do thumbnails for images that are saved in core data, which are being synced across multiple devices.
I know I can save a lower quality version into core data, but I'm wondering if there's a better way of doing it.
I've come across quick look thumbnailing which looks like what I want, but I'm not sure if it can be adapted for core data as its using file paths, whereas the images are stored in a data type property in core data.
From what I can tell, I'd have to save the image locally, produce the thumbnail, then delete the local image
So I’m trying to setup an existing app to work with iCloud syncing. The syncing part seems to be working for the most part.
When the app is first installed it sets up some data locally.
Throughout the life of the app additional data is added through updates. There is a user default setup that stores the current data version, then compares it with the new version. If it’s newer, then loads the additional data.
The issue I’ve got is a user can delete the app and reinstall, or install on another device which has that data version as 0, prompting another import even though the data in the cloud is current version, resulting in duplicate data once the sync is done.
How can I persist that version data? I’ve seen NSUbiquitousKeyValueStore which seems to be a cloud based version of user defaults, but it says not to rely on it if it’s critical to app functions
Topic:
App & System Services
SubTopic:
iCloud & Data
I've got a List containing Colour objects. Each colour may have an associated Project Colour object.
What I'm trying to do is set it up so that you can tap a cell and it will add/remove a project colour.
The adding/removing is working, but each time I do so, it appears the whole view is reloaded, the scroll position is reset and any predicate is removed.
This code I have so far
List {
ForEach(colourList) { section in
let header : String = section.id
Section(header: Text(header)) {
ForEach(section) { colour in
HStack {
if checkIfProjectColour(colour: colour) {
Image(systemName: "checkmark")
}
VStack(alignment: .leading){
HStack {
if let name = colour.name {
Text(name)
}
}
}
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
if checkIfProjectColour(colour: colour) {
removeProjectColour(colour: colour)
} else {
addProjectColour(colour: colour)
}
}
}
}
}
.onAppear() {
filters = appSetting.filters
colourList.nsPredicate = getFilterPredicate()
print("predicate: on appear - \(String(describing: getFilterPredicate()))")
}
.refreshable {
viewContext.refreshAllObjects()
}
}
.searchable(text: $searchText)
.onSubmit(of: .search) {
colourList.nsPredicate = getFilterPredicate()
}
.onChange(of: searchText) {
colourList.nsPredicate = getFilterPredicate()
}
The checkIfProjectColour function
func checkIfProjectColour(colour : Colour) -> Bool {
if let proCols = project.projectColours {
for proCol in proCols {
let proCol = proCol as! ProjectColour
if let col = proCol.colour {
if col == colour {
return true
}
}
}
}
return false
}
and the add/remove functions
func addProjectColour(colour : Colour) {
let projectColour = ProjectColour(context: viewContext)
projectColour.project = project
projectColour.colour = colour
colour.addToProjectColours(projectColour)
project.addToProjectColours(projectColour)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
func removeProjectColour(colour: Colour) {
if let proCols = project.projectColours {
for proCol in proCols {
let proCol = proCol as! ProjectColour
if let col = proCol.colour {
if col == colour {
viewContext.delete(proCol)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
}
}
I used to download and replace the app container when I was testing, essentially downloading the container from the live app, and restoring it into the test app in order to not affect the live app, but to test major changes on "live" data.
it seems the option for downloading and replacing in Xcode no longer works, I will sometimes get a container downloaded, other times it only part downloads. I can never seem to get it to replace. No errors, but it doesn't work on the new device.
It used to be that devices & simulators showed when it was downloading and replacing but it no longer does that.
Is there another way of doing this? Currently I'm having to take a backup of the live phone, restore on the test device, then delete all the unneeded apps, otherwise the restore takes ages, then backup the test device and restore every time I need to restart.
I have a grid setup where I'm displaying multiple images which is working fine. Images are ordered by the date they're added, newest to oldest.
I'm trying to set it up so that the user can change the sort order themselves but am having trouble getting the view to update.
I'm setting the fetch request using oldest to newest as default when initialising the view, then when its appears updating the sort descriptor
struct ProjectImagesListView: View {
@Environment(\.managedObjectContext) private var viewContext
var project : Project
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
@FetchRequest var pictures: FetchedResults<Picture>
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(pictures) { picture in
NavigationLink(destination: ProjectImageDetailView(picture: picture)) {
if let pictureData = picture.pictureThumbnailData, let uiImage = UIImage(data: pictureData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.frame(height: 100)
} else {
Image("missing")
.resizable()
.scaledToFit()
.frame(height: 100)
}
}
}
}
}
.navigationBarTitle("\(project.name ?? "") Images", displayMode: .inline)
.onAppear() {
guard let sortOrder = getSettingForPhotoOrder() else { return }
guard let sortOrderValue = sortOrder.settingValue else { return }
NSLog("sortOrderPhotos: \(String(describing: sortOrder.settingValue))")
if sortOrderValue == "Newest" {
NSLog("sortOrderPhotos: Change from default")
let newSortDescriptor = NSSortDescriptor(keyPath: \Picture.dateTaken, ascending: false)
pictures.nsSortDescriptors = [newSortDescriptor]
}
}
}
func getSettingForPhotoOrder() -> Setting? {
let fetchRequest: NSFetchRequest<Setting> = Setting.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name = %@", "photoSortOrder")
fetchRequest.fetchLimit = 1
do {
let results = try viewContext.fetch(fetchRequest)
return results.first
} catch {
print("Fetching Failed")
}
return nil
}
init(project: Project) {
self.project = project
_pictures = FetchRequest(
entity: Picture.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Picture.dateTaken, ascending: true)
],
predicate: NSPredicate(format: "project == %@", project)
)
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
I'm trying to convert some data, then save it back to Core Data. Sometimes this works fine without an issue, but occasionally I'll get an error
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
It seems to occur when saving the core data context. I'm having trouble trying to debug it as it doesn't happen on the same object each time and can't reliably recreate the error
Full view code can be found https://pastebin.com/d974V5Si but main functions below
var body: some View {
VStack {
// Visual code here
}
.onAppear() {
DispatchQueue.global(qos: .background).async {
while (getHowManyProjectsToUpdate() > 0) {
leftToUpdate = getHowManyProjectsToUpdate()
updateLocal()
}
if getHowManyProjectsToUpdate() == 0 {
while (getNumberOfFilesInDocumentsDirectory() > 0) {
deleteImagesFromDocumentsDirectory()
}
if getNumberOfFilesInDocumentsDirectory() == 0 {
DispatchQueue.main.asyncAfter(deadline: .now()) {
withAnimation {
self.isActive = true
}
}
}
}
}
}
}
update local function
func updateLocal() {
autoreleasepool {
let fetchRequest: NSFetchRequest<Project> = Project.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "converted = %d", false)
fetchRequest.fetchLimit = 1
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Project.name, ascending: true), NSSortDescriptor(keyPath: \Project.name, ascending: true)]
do {
let projects = try viewContext.fetch(fetchRequest)
for project in projects {
currentPicNumber = 0
currentProjectName = project.name ?? "Error loading project"
if let projectMain = project.mainPicture {
currentProjectImage = getUIImage(picture: projectMain)
}
if let pictures = project.pictures {
projectPicNumber = pictures.count
// Get main image
if let projectMain = project.mainPicture {
if let imgThumbData = convertImageThumb(picture: projectMain) {
project.mainPictureData = imgThumbData
}
}
while (getTotalImagesToConvertForProject(project: project ) > 0) {
convertImageBatch(project: project)
}
project.converted = true
saveContext()
viewContext.refreshAllObjects()
}
}
} catch {
print("Fetch Failed")
}
}
}
convertImageBatch function
func convertImageBatch(project: Project) {
autoreleasepool {
let fetchRequestPic: NSFetchRequest<Picture> = Picture.fetchRequest()
let projectPredicate = NSPredicate(format: "project = %@", project)
let dataPredicate = NSPredicate(format: "pictureData == NULL")
fetchRequestPic.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [projectPredicate, dataPredicate])
fetchRequestPic.fetchLimit = 5
fetchRequestPic.sortDescriptors = [NSSortDescriptor(keyPath: \Picture.dateTaken, ascending: true)]
do {
let pictures = try viewContext.fetch(fetchRequestPic)
for picture in pictures {
currentPicNumber = currentPicNumber + 1
if let imgData = convertImage(picture: picture), let imgThumbData = convertImageThumb(picture: picture) {
// Save Converted
picture.pictureData = imgData
picture.pictureThumbnailData = imgThumbData
// Save Image
saveContext()
viewContext.refreshAllObjects()
} else {
viewContext.delete(picture)
saveContext()
viewContext.refreshAllObjects()
}
}
} catch {
print("Fetch Failed")
}
}
}
And finally saving
func saveContext() {
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}