In my swift code below the goal is to save a uiimage using pngdata into core data. The problem is right now it does not appear to be saving because "test numbers" is not being printed into the debug section. I don't know how to make sure its being save. I am looking to save the image and verify its there.
override func viewDidLoad() {
super.viewDidLoad()
let gwen = UIImage(named: "f.jpeg")
if let imageData = gwen.self?.pngData() {
DataBaseHelper.shareInstance.saveImage(data: imageData)
}
let arr = DataBaseHelper.shareInstance.fetchImage()
print("test number : ",arr)
}
Other Class
class DataBaseHelper {
static let shareInstance = DataBaseHelper()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveImage(data: Data) {
let imageInstance = Info(context: context)
imageInstance.img = data
do {
try context.save()
print("Image is saved")
} catch {
print(error.localizedDescription)
}
}
func fetchImage() -> [Info] {
var fetchingImage = [Info]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Info")
do {
fetchingImage = try context.fetch(fetchRequest) as! [Info]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
It is a bad approach to saving image in core data!
For saving heavy documents iOS provides you document directory folder which is fast and efficient to save and retrieve than core data and user defaults.
Core data is an sqlite table which is just light weight properties like strings, numbers and Date etc.
Here is a trick you just save your image in document directory folder of your application which has large space and save the reference/ filename into core data.
Rather than creating data property you should have to create imageName property
import Foundation
import CoreData
extension Info {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Info> {
return NSFetchRequest<Info>(entityName: "Info")
}
#NSManaged public var imageName: String?
}
extension Info : Identifiable {
}
After that add this extension for save and get image from document folder
extension UIImage {
func save(to fileName:String) {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
let fileURL = documentsUrl.appendingPathComponent(fileName)
if let imageData = self.jpegData(compressionQuality: 1) {
try? imageData.write(to: fileURL, options: .atomic)
}
}
convenience init(fileName: String) {
var data = Data()
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
let fileURL = documentsUrl.appendingPathComponent(fileName)
do {
let imageData = try Data(contentsOf: fileURL)
data = imageData
} catch {
print(error.localizedDescription)
}
self.init(data: data)!
}
}
then how you can save and retrieve reference in core data and image document folder.
class ViewVC:UIViewController {
var images:[UIImage] = [] {
didSet {
print(images.count)
// reload tableView or collectionView
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
func saveImage(image:UIImage,fileName:String) {
image.save(to: fileName)
DataBaseHelper.shareInstance.saveImage(fileName: fileName)
}
func getImage() {
let allInfo = DataBaseHelper.shareInstance.fetchInfo()
for info in allInfo {
if let name = info.imageName {
let image = UIImage(fileName: name)
self.images.append(image)
}
}
}
}
you helper class
class DataBaseHelper {
static let shareInstance = DataBaseHelper()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveImage(fileName: String) {
let imageInstance = Info(context: context)
imageInstance.imageName = fileName
do {
try context.save()
print("Image name is saved")
} catch {
print(error.localizedDescription)
}
}
func fetchInfo() -> [Info] {
var fetchingImage = [Info]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Info")
do {
fetchingImage = try context.fetch(fetchRequest) as! [Info]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
Related
In my swift code below the goal is to print the number of items in the a core data attribute. Right now what is being printed out is not making sense to me. What is being printed out is 413091 and it should be 1. I assume that its printing out a number of how core data binary is saved but it should be 1 photo saved 1 item stored and print just 1.
import UIKit;import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let gwen = UIImage(named: "a.jpeg")
if let imageData = gwen.self?.pngData() {
DataBaseHelper.shareInstance.saveImage(data: imageData)
}
let arr = DataBaseHelper.shareInstance.fetchImage()
let jake = Int()
print("core data number is : ", arr[jake].img!.count)
}
}
class DataBaseHelper {
static let shareInstance = DataBaseHelper()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveImage(data: Data) {
let imageInstance = Image(context: context)
imageInstance.img = data
do {
try context.save()
print("Image is saved")
} catch {
print(error.localizedDescription)
}
}
func fetchImage() -> [Image] {
var fetchingImage = [Image]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Image")
do {
fetchingImage = try context.fetch(fetchRequest) as! [Image]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
Below is my main view controller. The user selects images of clothing which are then categorized using CoreML and given a filename. Then, data is saved to Realm. When I call the function loadClothing(), the array is empty even though items were added during func detect. Any help is much appreciated!
import UIKit
import PhotosUI
import RealmSwift
import CoreML
import Vision
class ViewController: UIViewController, PHPickerViewControllerDelegate {
#IBOutlet weak var shoesImageView: UIImageView!
#IBOutlet weak var shirtImageView: UIImageView!
#IBOutlet weak var pantsImageView: UIImageView!
var documentsUrl: URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
let realm = try! Realm()
var clothing: Results<Clothing>?
override func viewDidLoad() {
super.viewDidLoad()
loadClothing()
let clothingArray = Clothing()
print(clothingArray)
}
#IBAction func addClothesButton(_ sender: UIBarButtonItem) {
pickPhotos()
}
#IBAction func randomizeButton(_ sender: UIBarButtonItem) {
loadClothing()
let clothingArray = Clothing()
print(clothingArray)
shirtImageView.image = load(fileName: clothingArray.shirtImages.randomElement()!)
pantsImageView.image = load(fileName: clothingArray.pantsImages.randomElement()!)
shoesImageView.image = load(fileName: clothingArray.shoesImages.randomElement()!)
}
//MARK: - PHPickerViewController
#objc func pickPhotos() {
var config = PHPickerConfiguration()
config.selectionLimit = 25
config.filter = PHPickerFilter.images
let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = self
self.present(pickerViewController, animated: true, completion: nil)
}
// MARK: - PHPickerViewControllerDelegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
for result in results {
result.itemProvider.loadObject(ofClass: UIImage.self) {(object, error) in
if let image = object as? UIImage {
DispatchQueue.main.async {
guard let fileName = result.itemProvider.suggestedName else {
fatalError("Could not retrieve file name.")
}
print(fileName)
guard let ciImage = CIImage(image: image) else {
fatalError("Could not convert to CI Image.")
}
self.detect(image: ciImage, fileName: fileName)
}
}
}
}
}
// MARK: - Core ML
func detect(image: CIImage, fileName: String) {
guard let model = try? VNCoreMLModel(for: ClothingClassifier(configuration: MLModelConfiguration()).model) else {
fatalError("Loading CoreML Model failed.")
}
let request = VNCoreMLRequest(model: model) { (request, error) in
guard let results = request.results as? [VNClassificationObservation] else {
fatalError("Model failed to process image.")
}
let newClothing = Clothing()
if let firstResult = results.first {
let uiImage = UIImage(ciImage: image)
if firstResult.identifier.contains("shirts") {
newClothing.shirtImages.append(fileName)
} else if firstResult.identifier.contains("pants"){
newClothing.pantsImages.append(fileName)
} else if firstResult.identifier.contains("shoes") {
newClothing.shoesImages.append(fileName)
}
self.save(clothing: newClothing)
print(newClothing)
}
}
let handler = VNImageRequestHandler(ciImage: image)
do {
try handler.perform([request])
}
catch {
print(error)
}
}
// MARK: - Data Manipulation Methods
func save(clothing: Clothing) {
do {
try realm.write {
realm.add(clothing)
}
} catch {
print("Error saving uploaded clothing. \(error)")
}
}
func loadClothing() {
clothing = realm.objects(Clothing.self)
print("loaded")
}
private func load(fileName: String) -> UIImage? {
let fileURL = documentsUrl.appendingPathComponent(fileName)
do {
let imageData = try Data(contentsOf: fileURL)
return UIImage(data: imageData)
} catch {
print("Error loading image : \(error)")
}
return nil
}
}
Clothing Class
import Foundation
import RealmSwift
class Clothing: Object {
let shirtImages = List<String>()
let pantsImages = List<String>()
let shoesImages = List<String>()
}
I have a problem with loading images from firebase. I have two functions. One of them collect info about user, second one load users avatar image. Unfortunately images load after function creates new user. I know it will be problem with asynchronous of Firebase but I don't know how to set up DispatchQueue to work properly. Can you help me with that?
// function that load user image in user manager class
func loadUserImage(contactUserID: String, completion: #escaping (UIImage) -> Void) {
let userID = Auth.auth().currentUser!.uid
var userImageRef = self.storage.child("\(userID)/userImage.jpg")
var image = UIImage()
if contactUserID != "" {
userImageRef = self.storage.child("\(contactUserID)/userImage.jpg")
}
userImageRef.getData(maxSize: 5 * 1024 * 1024) { (data, error) in
if let error = error {
print("Error with retrieving data: \(error.localizedDescription)")
} else {
if data?.count != 0 {
image = UIImage(data: data!)!
} else {
image = UIImage(systemName: "person.circle.fill")!
}
completion(image)
}
}
}
// function that load user in contact manager class
func loadContactList(completion: #escaping ([User]) -> Void) {
let currentUserID = Auth.auth().currentUser!.uid
db.collection("contacts")
.document(currentUserID)
.collection("userContacts")
.addSnapshotListener { (querySnapshot, error) in
var contactList = [User]()
if let error = error {
print("Error with retrieving data from DB: \(error.localizedDescription)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for document in snapshotDocuments {
let data = document.data()
let uid = data["uid"] as! String
let name = data["name"] as! String
let email = data["email"] as! String
var contact = User(email: email, name: name, userID: uid)
DispatchQueue.global().sync {
self.userService.loadUserImage(contactUserID: uid) { (image) in
contact.photoURL = image
}
}
contactList.append(contact)
contactList.sort {
$0.name < $1.name
}
completion(contactList)
}
}
}
}
}
// Function implementation in viewController
func loadContactList() {
self.contactService.loadContactList { (contactArray) in
self.contactList = contactArray
self.tableView.reloadData()
}
}
What you can do is to store the image url in the firebase database and after that create this extension:
import UIKit
let imageCache: NSCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(urlString: String) {
self.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
let url = URL(string: urlString)
if let data = try? Data(contentsOf: url!) {
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
})
}
}
}
And call:
if let url = data["imgUrl"] as? String {
self.myImageView.loadImageUsingCacheWithUrlString(urlString: url)
}
For that what you need to do is to create and initialize an UIImage object. If you are working with cell classes you need to create this object in the cell.
tl;dr:
clone the project: https://github.com/Jasperav/CoreDataInMemoryFail
Run the test and see it fail. Why does my in memory container not have any data and how can I make sure it will have data?
Long:
I have a sqlite file with filled data and I have an in-memory database in CoreData. Some code:
// ...
func createInMemoryPerformanceTestDatabase() -> NSPersistentContainer {
let url = createPathToSomeSQLiteFile()
let container = NSPersistentContainer(name: dataModelName, managedObjectModel: objectModel)
let description = NSPersistentStoreDescription(url: url)
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { description, error in
XCTAssertNil(error)
}
return container
}
// ...
Although the sqlite file has data inside it, I don't see it back inside my contexts I create with container.
When I create an in-memory database with CoreData pointing to a sqlite file with data, I don't see any results when querying the database. I want to see the data inside the sqlite file. The data should just load all in memory. This is for testing purposes.
The problem with what you have tried was that you set the type of your storeDescription as NSInMemoryStoreType before loading them into the container. Since, the type of storeDescription is stated as NSInMemoryStoreType the api won't read and populate data from the file URL you have provided. In order for the api to read the data from the file url, the type of storeDescription must be the one defined by initialising with the initialiser init(url: URL) which is SQLite in your case.
However if you want to have a persistentStore of type NSInMemoryStoreType with data read from the file url, you can migrate the persistentStores of your persistentContainer with NSInMemoryStoreType type using function migratePersistentStore:toURL:options:withType:error:. You can try out the code snippet below.
import CoreData
import XCTest
#testable import CoreDataInMemoryFail
class CoreDataInMemoryFailTests: XCTestCase {
private func createContainer(modify: (NSPersistentContainer) -> ()) -> NSPersistentContainer {
let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: "InMemoryDatabase", ofType: "sqlite")!
let url = URL(fileURLWithPath: path)
let persistentContainer = createPersistentContainer(dataModelName: "InMemoryDatabase")
let storeDescription = NSPersistentStoreDescription(url: url)
persistentContainer.persistentStoreDescriptions = [storeDescription]
persistentContainer.loadPersistentStores { description, error in
XCTAssertEqual(storeDescription.type, description.type)
XCTAssertNil(error)
}
modify(persistentContainer)
return persistentContainer
}
func testFail() {
let persistentContainer = createContainer(modify: { _ in })
let inMemoryContainer = createContainer { persistentContainer in
let coordinator = persistentContainer.persistentStoreCoordinator
coordinator.persistentStores.forEach { (persistentStore) in
do {
try coordinator.migratePersistentStore(persistentStore, to: NSPersistentContainer.defaultDirectoryURL(), options: nil, withType: NSInMemoryStoreType)
} catch {
print("Error while migrating persistentStore")
}
}
}
let persistentContainerCoordinator = persistentContainer.persistentStoreCoordinator
persistentContainerCoordinator.persistentStores.forEach { (persistentStore) in
XCTAssertEqual(persistentStore.type, "SQLite")
}
let inMemoryContainerCoordinator = inMemoryContainer.persistentStoreCoordinator
inMemoryContainerCoordinator.persistentStores.forEach { (persistentStore) in
XCTAssertEqual(persistentStore.type, NSInMemoryStoreType)
}
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let persistentContainerCount = (try! persistentContainer.viewContext.fetch(fetchRequest)).count
let inMemoryContainerCount = (try! inMemoryContainer.viewContext.fetch(fetchRequest)).count
XCTAssertEqual(8, persistentContainerCount)
XCTAssertEqual(persistentContainerCount, inMemoryContainerCount)
}
}
In the above snippet, I have also added asserts to verify whether persistentStore type is NSInMemoryStoreType in your inMemoryContainer and SQLite in your persistentContainer. Hope it helps.
The InMemoryType is not loading the date from your url as the other answer suggests. If you need to load the data from the file then please use the Migrate approach mentioned however if you only need to fill it with random data for testing purposes then here is another solution.
import CoreData
import XCTest
#testable import CoreDataInMemoryFail
class CoreDataInMemoryFailTests: XCTestCase {
var persistentContainer: NSPersistentContainer!
var inMemoryContainer: NSPersistentContainer!
override func setUp() {
super.setUp()
persistentContainer = createContainer(modify: { _ in })
inMemoryContainer = createContainer { storeDescription in
storeDescription.type = NSInMemoryStoreType
}
initStubs()
}
override class func tearDown() {
super.tearDown()
}
private func createContainer(modify: (NSPersistentStoreDescription) -> ()) -> NSPersistentContainer {
let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: "InMemoryDatabase", ofType: "sqlite")!
let url = URL(fileURLWithPath: path)
let fileManager = FileManager.default
let uuid = UUID().uuidString
let saveDirectory = fileManager
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent(uuid)
let saveLocation = saveDirectory.appendingPathComponent(url.lastPathComponent)
try! fileManager.createDirectory(at: saveDirectory, withIntermediateDirectories: false)
try! fileManager.copyItem(at: url, to: saveLocation)
let persistentContainer = createPersistentContainer(dataModelName: "InMemoryDatabase")
let storeDescription = NSPersistentStoreDescription(url: saveLocation)
modify(storeDescription)
print("TYPE OF STORE IS: \(storeDescription)")
persistentContainer.persistentStoreDescriptions = [storeDescription]
persistentContainer.loadPersistentStores { description, error in
XCTAssertEqual(storeDescription.type, description.type)
XCTAssertNil(error)
}
return persistentContainer
}
func initStubs() {
func inserPerson( age: Int32) -> Person? {
let obj = NSEntityDescription.insertNewObject(forEntityName: "Person", into: inMemoryContainer.viewContext)
obj.setValue(age, forKey: "age")
return obj as? Person
}
_ = inserPerson(age: 1)
_ = inserPerson(age: 2)
_ = inserPerson(age: 3)
_ = inserPerson(age: 4)
_ = inserPerson(age: 5)
do {
try inMemoryContainer.viewContext.save()
} catch {
print("create fakes error \(error)")
}
}
func removeData() {
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let objs = try! inMemoryContainer.viewContext.fetch(fetchRequest)
for case let obj as NSManagedObject in objs {
inMemoryContainer.viewContext.delete(obj)
}
try! inMemoryContainer.viewContext.save()
}
func testFail() {
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let persistentContainerCount = (try! persistentContainer.viewContext.fetch(fetchRequest)).count
let inMemoryContainerCount = (try! inMemoryContainer.viewContext.fetch(fetchRequest)).count
XCTAssertEqual(8, persistentContainerCount)
XCTAssertEqual(5, inMemoryContainerCount)
}
}
More info can be found here
I have 2 functions saveImage and loadImage:
//saveImage
func saveImage(imageName: String, image: UIImage) {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileName = imageName
let fileURL = documentsDirectory.appendingPathComponent(fileName)
guard let data = image.jpegData(compressionQuality: 1) else { return }
if FileManager.default.fileExists(atPath: fileURL.path) {
do {
try FileManager.default.removeItem(atPath: fileURL.path)
print("Removed old image")
} catch let removeError {
print("couldn't remove file at path", removeError)
}
}
do {
try data.write(to: fileURL)
} catch let error {
print("error saving file with error", error)
}
}
//loadImage
func loadImageFromDocuments(fileName: String) -> UIImage? {
let documentDirectory = FileManager.SearchPathDirectory.documentDirectory
let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true)
if let dirPath = paths.first {
let imageUrl = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName)
let image = UIImage(contentsOfFile: imageUrl.path)
return image
}
return nil
}
}
When I call in tableviewcelll like this:
self.cachedImageView.saveImage(imageName:,image:)
self.cachedImageView.loadImageFromDocuments(fileName:)
I don't how know use that.
Create image loader class like below :-
class PKImageLoader {
let imageCache = NSCache<NSString, UIImage>()
class var sharedLoader: PKImageLoader {
struct Static {
static let instance: PKImageLoader = PKImageLoader()
}
return Static.instance
}
func imageForUrl(urlPath: String, completionHandler: #escaping (_ image: UIImage?, _ url: String) -> ()) {
guard let url = urlPath.toUrl else {
return
}
if let image = imageCache.object(forKey: urlPath as NSString) {
completionHandler(image, urlPath)
}
else {
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let finalData = data else { return }
DispatchQueue.main.async {
if let img = UIImage(data: finalData) {
self.imageCache.setObject(img, forKey: urlPath as NSString)
completionHandler(img, urlPath)
}
}
}.resume()
}
}
}
download an image from URL and save it (by creating UIImage Array)
you can use that array as you want.
use below extension for setting image directly to image view.
extension UIImageView {
func setImage(from urlPath: String, placeHolder: UIImage? = nil) {
self.image = placeHolder
PKImageLoader.sharedLoader.imageForUrl(urlPath: urlPath) { image, _ in
self.image = image
}
}
this one also help you
extension String {
var toUrl: URL? {
if self.hasPrefix("https://") || self.hasPrefix("http://") {
return URL(string: self)
}
else {
return URL(fileURLWithPath: self)
}
}