Core data NSManagedObject convert to json format - core-data

Hi guys I need your help I am new in swift language now I Stuck here I need json like this format from core data result. thanks in advance
Core Data Function return this result and I want to convert this result info json format
func GetAttendance()->[Tbl_Attendance]
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Tbl_Attendance")
do {
let fetchedAttendance = try context.fetch(fetchRequest) as! [Tbl_Attendance]
return fetchedAttendance
} catch {
fatalError("Failed to fetch Tbl_Attendance: \(error)")
}
}
I need to convert like this
{
"students": [
{
"id": 12345,
"name": "Giuseppe",
"lastName": "Lanza",
"age": 31
}
]
}

Create extension to Tbl_Attendance and make it implement Encodable
Create enum CodingKeys: String, CodingKey {...} in extension containing all attributes to encode
Implement public func encode(to encoder: Encoder) in extension
From somewhere in your code perform encoding
Example
extension Tbl_Attendance: Encodable {
enum CodingKeys: String, CodingKey {
case prop1
case prop2
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(prop1, forKey: .prop1)
try container.encode(prop2, forKey: .prop2)
}
}
//perform encoding somewhere in your code
let arr: GetAttendance()
let encoder = JSONEncoder()
do {
let json = try encoder.encode(arr)
//print(String(data: json, encoding: .utf8)!)
} catch {
print("JSON error \(error)")
}

Related

SwiftUI ForEach force UI update when updating the contents of a core data relationship

My app is meant to have a bunch of workouts in core data, each with a relationship to many exercises. A view should display the data in each workout (name, description etc.) and then iterate and display each exercise belonging to that workout.
Adding exercises and displaying them works fine. If an exercise is deleted, however it:
deletes from coredata no worries
the information seems to delete from iterableExercises
however, the Text line does not disappear. it goes from, for example "Squat, Description" to simply " , "
If I close the app entirely and reopen, then the " , " lines do completely disappear.
The problem code:
if let iterableExercises = workout.exercises?.array as? [ExerciseEntity] {
ForEach(iterableExercises) {exercise in
Text("\(exercise.name ?? ""), \(exercise.desc ?? "")")
}
}
I've got the entity relationship set as ordered, but I've also tried unordered with .allObjects instead of .array. This clearly isn't the problem as it's the array iterableExercises that's not correctly being reset?
EDIT: to reproduce, here's all the code you need and some screenshots of the CoreData model.
import SwiftUI
import CoreData
class ViewModel: ObservableObject {
let container: NSPersistentCloudKitContainer
#Published var savedWorkouts: [WorkoutEntity] = []
#Published var savedExercises: [ExerciseEntity] = []
// MARK: INIT
init() {
container = NSPersistentCloudKitContainer(name: "mre")
container.loadPersistentStores { description, error in
if let error = error {
print("Error loading CoreData: \(error)")
}
}
fetchWorkoutEntities()
fetchExerciseEntities()
}
// MARK: FETCHERS
func fetchWorkoutEntities() {
let request = NSFetchRequest<WorkoutEntity>(entityName: "WorkoutEntity")
do {
savedWorkouts = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching WorkoutEntity: \(error)")
}
}
func fetchExerciseEntities() {
let request = NSFetchRequest<ExerciseEntity>(entityName: "ExerciseEntity")
do {
savedExercises = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching ExerciseEntity: \(error)")
}
}
// MARK: SAVE
func saveData() {
do {
try container.viewContext.save()
fetchWorkoutEntities()
fetchExerciseEntities()
} catch let error {
print("Error saving: \(error)")
}
}
// MARK: ADDERS
func addWorkout(name: String) {
let _ = WorkoutEntity(context: container.viewContext)
saveData()
}
func addExerciseToWorkout(workout: WorkoutEntity, name: String) {
let newExercise = ExerciseEntity(context: container.viewContext)
newExercise.name = name
workout.addToExercises(newExercise)
saveData()
}
// MARK: DELETERS
func deleteWorkout(workout: WorkoutEntity) {
container.viewContext.delete(workout)
saveData()
}
func deleteExercise(exercise: ExerciseEntity) {
container.viewContext.delete(exercise)
saveData()
}
// MARK: TODO: UPDATERS
}
struct ContentView: View {
#StateObject var data = ViewModel()
var body: some View {
VStack {
Button {
data.addWorkout(name: "workout")
data.addExerciseToWorkout(workout: data.savedWorkouts[0], name: "[exercisename]")
} label: {
Text("Click ONCE to add workout to work with")
}
Spacer()
if let iterableExercises = data.savedWorkouts[0].exercises?.array as? [ExerciseEntity] {
ForEach(iterableExercises) { exercise in
Button {
data.deleteExercise(exercise: exercise)
} label: {
Text("Click to delete \(exercise.name ?? "") AFTER DELETING IF THIS STILL SHOWS BUT DOESN'T SHOW THE EXERCISE NAME THEN IT'S BROKEN")
}
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
screenshots of model
I’m not sure if this is the ONLY solution as #malhal gave quite an extensive and seemingly useful response.
But I came across a much easier and immediate fix, within my original solution. The inverse relationships must be specified. Doing this resolved all issues.
We don't use view model objects in SwiftUI. You need to learn the View struct and property wrappers which gives the consistency and efficiency of value types with the benefits of reference types. The property wrapper for core data is #FetchRequest which invalidates the View when the results change. It's also a DynamicProperty (which is how it gets the context from the environment) that you can use it directly without the property wrapper syntax which allows you to use a param in a predicate, in your case to do fetch the one-to-many relation, e.g.
struct WorkoutView: View {
private var fetchRequest: FetchRequest<Exercise>
private var exercices: FetchedResults<Exercise> {
fetchRequest.wrappedValue
}
init(workout: Workout) {
let sortAscending = true
let sortDescriptors = [SortDescriptor(\Exercise.timestamp, order: sortAscending ? .forward : .reverse)]
fetchRequest = FetchRequest(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "workout = %#", workout), animation: .default)
}
var body: some View {
List(exercises) { exercise in
ExerciseView(exercise: exercise)
}
}
}
For creating the NSPersistentContainer check out the Xcode App template with Core Data checked. Looks like this:
#main
struct TestApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
The reason it is not an #StateObject is we don't want to invalidate this body when it changes and we need it to be init for previewing which is a different singleton.
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
... see template
That other code in your view model class can be moved to NSManagedObject and NSManagedObjectContext extensions. Use the Editor menu to generate the NSManagedObject extension for the model, the files need tidying up though and make sure use extension is selected for the entity.

SwiftUI Text Markdown dynamic string not working

I want to dynamically deliver content and display hyperlinks, but it can’t be delivered dynamically and doesn’t work
let linkTitle = "Apple Link"
let linkURL = "http://www.apple.com"
let string = "[Apple Link](http://www.apple.com)"
Text(string) // Not working
Text("[Apple Link](http://www.apple.com)") // Working
Text("[\(linkTitle)](http://www.apple.com)") // Working
Text("[\(linkTitle)](\(linkURL))") // Not working
Short Answer
Wrap the string in AttributedString(markdown: my_string_here):
let string: String = "[Apple Link](http://www.apple.com)"
Text(try! AttributedString(markdown: string))
Extension
extension String {
func toMarkdown() -> AttributedString {
do {
return try AttributedString(markdown: self)
} catch {
print("Error parsing Markdown for string \(self): \(error)")
return AttributedString(self)
}
}
}
Long Answer
SwiftUI Text has multiple initializers.
For String:
init<S>(_ content: S) where S : StringProtocol
For AttributedString:
init(_ attributedContent: AttributedString)
When you declare a static string, Swift is able to guess whether the intent is to use a String or AttributedString (Markdown). However, when you use a dynamic string, Swift needs help in figuring out your intent.
As a result, with a dynamic string, you have to explicitly convert your String into an AttributedString:
try! AttributedString(markdown: string)
you can try this taken from: How to show HTML or Markdown in a SwiftUI Text? halfway down the page.
extension String {
func markdownToAttributed() -> AttributedString {
do {
return try AttributedString(markdown: self) /// convert to AttributedString
} catch {
return AttributedString("Error parsing markdown: \(error)")
}
}
}
struct ContentView: View {
let linkTitle = "Apple Link"
let linkURL = "https://www.apple.com"
let string = "[Apple Link](https://www.apple.com)"
#State var textWithMarkdown = "[Apple Link](https://www.apple.com)"
var body: some View {
VStack {
Text(textWithMarkdown.markdownToAttributed()) // <-- this works
Text(string) // Not working
Text("[Apple Link](http://www.apple.com)") // Working
Text("[\(linkTitle)](http://www.apple.com)") // Working
Text("[\(linkTitle)](\(linkURL))") // Not working
}
}
}
Add another .init in text.
struct ContentView: View {
let linkTitle = "Apple Link"
let linkURL = "http://www.apple.com"
let string = "[Apple Link](http://www.apple.com)"
var body: some View {
Text(.init(string)) // <== Here!
Text("[Apple Link](http://www.apple.com)") // Working
Text("[\(linkTitle)](http://www.apple.com)") // Working
Text(.init("[\(linkTitle)](\(linkURL))")) // <== Here!
}
}

Nesting URLSession.shared.dataTask in Swift 4

I am trying to fetch data from an api where the JSON returned has URLs to other pieces of information that I need, such as
"value1" : "data",
"value2": {
"url": "https://example.com/stuff",
}
My logic is as follows:
func(completion: #escaping ([Data]) -> ()) {
var classArray = [myClass]()
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
guard let resultArray = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
let myObject = myClass(value1: resultArray["value1"]! as! String)
guard let valueUrl = URL(string: resultArray["value2"]! as! String) else { return }
URLSession.shared.dataTask(with: valueUrl) { (data, _, _) in
myObject.value2 = data
classArray.append(myObject)
}.resume()
} catch let error {
print("Failed to create json with error: ", error.localizedDescription)
}
completion(classArray)
}.resume()
}
}
Is this a valid approach or are there better implementations? Trying to avoid a future Pyramid of Doom situation. I have tried putting the inner URLSession call in a separate private function but still receive an empty classArray in the end.

Core Data entries not updating correctly in swift3

I have a core data entries with certain keys and values stored in the database fetched from API . Now i want to update some core data entries with the updated values So for this i am using the code as below:
func updateAllRecords(responseArray: [Dictionary<String, String>]) {
for settingsObject in responseArray {
if #available(iOS 10.0, *) {
let keys = settingsObject.flatMap() { $0.0 as? String }
let values = settingsObject.flatMap(){ $0.1 as? String}
let request = NSFetchRequest<ApplicationSettings>(entityName: "ApplicationSettings")
do {
let context = persistentContainer.viewContext
let searchResults = try context.fetch(request)
for settingsKeys in searchResults {
if keys.contains(settingsKeys.key!) {
settingsKeys.value = values[1]
try context.save()
}
}
} catch {
print ("There was an error")
}
} else {
}
}
}
}
I am calling this function from viewcontroller like this:
var dicArray = [Dictionary<String, String>]()
let dic1 = ["IS_CIVIL_ID_MANDATORY": "Smith", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert"]
dicArray.append(dic1)
CoreDataHandler.sharedInstance.updateAllRecords(responseArray: dicArray)
But the entries in the table is not updating correctly for the keys. It is storing the same value for both the keys. See below:
Where i am wrong in this code? Why same values are stored for the keys?
You made mistake here
settingsKeys.value = values[1] here 1 is static
I think you don't require values array separate
just replace this code with my code
for settingsKeys in searchResults {
if keys.contains(settingsKeys.key!)
{
settingsKeys.updateValue(settingObject[settingsKeys.key!], forKey:settingsKeys.key!)
try context.save()
}
}
}
For Demo Example
func updateAllRecords(responseArray: [Dictionary<String, String>])
{
for settingsObject in responseArray
{
var dic2 = ["IS_CIVIL_ID_MANDATORY": "xyz", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "xyz"]
let keys = settingsObject.flatMap() {$0.0}
let values = settingsObject.flatMap(){$0.1}
let settingKeys = dic2.flatMap() {$0.0}
for settingsKey in settingKeys
{
dic2.updateValue(settingsObject[settingsKey]!, forKey: settingsKey)
}
print(keys)
print(dic2)
}
}
var dicArray = [Dictionary<String, String>]()
let dic1 = ["IS_CIVIL_ID_MANDATORY": "Smith", "TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert"]
dicArray.append(dic1)
updateAllRecords(responseArray: dicArray)
Output
["TIME_IN_MIN_BEFORE_ENABLING_START_TRIP": "Robert", "IS_CIVIL_ID_MANDATORY": "Smith"]

Convert Data to String in Swift 3

I am very new to Swift.
I want to create something like API on Swift for my educational app.
I have this code:
static func getFilm(filmID: Int) -> String {
print("getFilm")
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
var request = URLRequest(url: url)
var returnData: String = ""
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if var responseVar = response, var dataVar = data {
print(responseVar)
returnData = String(data: dataVar, encoding: .utf8)
} else {
print(error)
}
}
task.resume()
return returnData
}
And I try to convert Data to String in this line: returnData = String(data: dataVar, encoding: .utf8)
Swift compiler gives me an error, and change this line to
returnData = String(data: dataVar, encoding: .utf8)!
, when I execute this line I get empty returnData variable.
If I use basic example line
print(String(data: data, encoding: .utf8))
everything will be OK and I can see data in XCode console.
So, how I can convert Data to String?
This is an example using a completion handler:
class func getFilm(filmID: Int, completion: #escaping (String) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
print(error!)
completion("")
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(returnData)
} else {
completion("")
}
}
}.resume()
}
And you call it
MyClass.getFilm(filmID:12345) { result in
print(result)
}
In case of an error the completion handler returns an empty string.
MyClass is the enclosing class of getFilm method. Most likely the web service will return JSON, so you might need to deserialize the JSON to an array or dictionary.
In a more sophisticated version create an enum with two cases and associated values
enum ConnectionResult {
case success(String), failure(Error)
}
With a little more effort demonstrating the subtle power of Swift you can return either the converted string on success of the error on failure in a single object.
class func getFilm(filmID: Int, completion: #escaping (ConnectionResult) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
completion(.failure(error!))
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(.success(returnData))
} else {
completion(.failure(NSError(domain: "myDomain", code: 9999, userInfo: [NSLocalizedDescriptionKey : "The data is not converible to 'String'"])))
}
}
}.resume()
}
On the caller side a switch statement separates the cases.
MyClass.getFilm(filmID:12345) { result in
switch result {
case .success(let string) : print(string)
case .failure(let error) : print(error)
}
}
I had this problem, you can't use encoding: .utf8 for unpredictable data. It will return nil every time.
Use this instead:
String(decoding: data, as: UTF8.self)
For anyone coming in future (which are probably not interested in OP's film code?!);
Simply, try something like:
extension Data {
public func toString() -> String {
return String(data: self, encoding: .utf8) ?? "";
}
}
See also my toHex related answer

Resources