I am trying to currently display the album artwork for a locally stored .MP3 track in an ImageView. Does anyone know how to fetch this artwork in Swift in order to accomplish this?
I have found this solution (iOS AVFoundation: How do I fetch artwork from an mp3 file?) but the code is written in Objective C. I simply want to grab the image embedded in my MP3 and display it in my ImageView.
I've looked at the API documentation for the MPMediaItemArtwork and found an example that also accomplishes what I am trying to accomplish in Objective C as well here(http://www.codeitive.com/0zHjkUjUWX/not-able-to-get-the-uiimage-from-mpmediaitempropertyartwork.html) but cannot come up with a solution. My code is as follows:
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController {
let audioPath:NSURL! = NSBundle.mainBundle().URLForResource("SippinOnFire", withExtension: "mp3")
#IBOutlet var artistImage: UIImageView!
#IBOutlet var trackLabel: UILabel!
#IBOutlet var artistLabel: UILabel!
#IBOutlet var sliderValue: UISlider!
var player:AVAudioPlayer = AVAudioPlayer()
#IBAction func play(sender: AnyObject) {
let audioInfo = MPNowPlayingInfoCenter.defaultCenter()
println(audioInfo)
player.play()
//println("Playing \(audioPath)")
let playerItem = AVPlayerItem(URL: audioPath)
let metadataList = playerItem.asset.metadata as! [AVMetadataItem]
for item in metadataList {
if let stringValue = item.value {
println(item.commonKey)
if item.commonKey == "title" {
trackLabel.text = stringValue as? String
}
if item.commonKey == "artist" {
artistLabel.text = stringValue as? String
}
if item.commonKey == "artwork" {
if let audioImage = UIImage(data: item.value as! NSData) {
let audioArtwork = MPMediaItemArtwork(image: audioImage)
println(audioImage.description)
}
}
}
}
}
#IBAction func pause(sender: AnyObject) {
player.pause()
}
#IBAction func stop(sender: AnyObject) {
player.stop()
player.currentTime = 0;
}
#IBAction func sliderChanged(sender: AnyObject) {
player.volume = sliderValue.value
}
override func viewDidLoad() {
super.viewDidLoad()
var error:NSError? = nil
player = AVAudioPlayer(contentsOfURL: audioPath!, error: &error)
player.volume = 0.5;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is a screen shot of my sample .mp3 file. As you can see there is indeed album artwork that is both visible in the "get info" section of Finder. I've also opened the .mp3 in my iTunes to make sure and have confirmed there is artwork in the "get info" section of it there as well as under the "artwork" tab.
However, when trying to use the commonKey to assign the image to my imageView I find that there is no commonKey for "artwork".
Thanks
Change your snippet of code into this (I already tested it):
I added println lines commented in places of interest, Feel free to uncomment in order to see what is happening.
for item in metadataList {
if item.commonKey == nil{
continue
}
if let key = item.commonKey, let value = item.value {
//println(key)
//println(value)
if key == "title" {
trackLabel.text = value as? String
}
if key == "artist" {
artistLabel.text = value as? String
}
if key == "artwork" {
if let audioImage = UIImage(data: value as! NSData) {
//println(audioImage.description)
artistImage.image = audioImage
}
}
}
}
UPDATE: A bit of clean up of this code
for item in metadataList {
guard let key = item.commonKey, let value = item.value else{
continue
}
switch key {
case "title" : trackLabel.text = value as? String
case "artist": artistLabel.text = value as? String
case "artwork" where value is NSData : artistImage.image = UIImage(data: value as! NSData)
default:
continue
}
}
UPDATE: For Swift 4
for item in metadataList {
guard let key = item.commonKey?.rawValue, let value = item.value else{
continue
}
switch key {
case "title" : trackLabel.text = value as? String
case "artist": artistLabel.text = value as? String
case "artwork" where value is Data : artistImage.image = UIImage(data: value as! Data)
default:
continue
}
}
edit/update Swift 4 or later:
import MediaPlayer
var nowPlayingInfo: [String: Any] = [:]
let playerItem = AVPlayerItem(url: url)
let metadataList = playerItem.asset.metadata
for item in metadataList {
switch item.commonKey {
case .commonKeyTitle?:
nowPlayingInfo[MPMediaItemPropertyTitle] = item.stringValue ?? ""
case .commonKeyType?:
nowPlayingInfo[MPMediaItemPropertyGenre] = item.stringValue ?? ""
case .commonKeyAlbumName?:
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = item.stringValue ?? ""
case .commonKeyArtist?:
nowPlayingInfo[MPMediaItemPropertyArtist] = item.stringValue ?? ""
case .commonKeyArtwork?:
if let data = item.dataValue,
let image = UIImage(data: data) {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
}
case .none: break
default: break
}
}
let audioInfo = MPNowPlayingInfoCenter.default()
audioInfo.nowPlayingInfo = nowPlayingInfo
Note: You will have to invoke beginReceivingRemoteControlEvents() otherwise it will not work on the actual device. You will also need to set your app Background Modes (Audio and AirPlay) and set your AVAudioSession category to AVAudioSessionCategoryPlayback and set it active:
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
Try this:
It appears that sometimes iOS 8 returns nil at first attempt of obtaining this info:
if let audioCenter = MPNowPlayingInfoCenter.defaultCenter(){
if let audioInfo = audioCenter.nowPlayingInfo{
if let artwork = audioInfo[MPMediaItemPropertyArtwork] as? MPMediaItemArtwork
{
var image: UIImage? = artwork.imageWithSize(artwork.bounds.size)
if image == nil {
image = artwork.imageWithSize(artwork.bounds.size);
}
if image != nil{
println("image loaded")
}
}
}
}
Related
I sent my data from my API call to my InfoController viewDidLoad. There, I was able to safely store it in a skillName constant, and also printed it, receiving all the information by console.
The problem comes when I try to assign this variable to my skillLabel.
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
guard var skillName = names as? String else { return }
self.pokemon?.skillName = skillName
self.allNames = skillName
print(self.allNames)
}
}
There, when I print allNames, the console shows all the data I need. This is how the data looks like: Data Example
And the computed property where I wanna use this data looks is:
var pokemon: Pokemon? {
didSet {
guard let id = pokemon?.id else { return }
guard let data = pokemon?.image else { return }
navigationItem.title = pokemon?.name?.capitalized
infoLabel.text = pokemon?.description
infoView.pokemon = pokemon
if id == pokemon?.id {
imageView.image = UIImage(data: data)
infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: "\(allNames)")
}
}
}
PD: allNames is a String variable I have at InfoController class-level.
This is how my app looks when run:
PokeApp
My goal is to get that details param to show the skillName data, but it returns nil, idk why. Any advice?
EDIT1: My func that fetches the Pokemon data from my service class is this one:
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
DispatchQueue.main.async {
self.pokemon? = poke
guard let skills = poke.abilities else { return }
for skill in skills {
guard let ability = skill.ability else { return }
guard var names = ability.name!.capitalized as? String else { return }
self.pokemon?.skillName = names
handler(names)
}
}
}
}
EDIT2: InfoView class looks like:
class InfoView: UIView {
// MARK: - Properties
var delegate: InfoViewDelegate?
// This whole block assigns the attributes that will be shown at the InfoView pop-up
// It makes the positioning of every element possible
var pokemon: Pokemon? {
didSet {
guard let pokemon = self.pokemon else { return }
guard let type = pokemon.type else { return }
guard let defense = pokemon.defense else { return }
guard let attack = pokemon.attack else { return }
guard let id = pokemon.id else { return }
guard let height = pokemon.height else { return }
guard let weight = pokemon.weight else { return }
guard let data = pokemon.image else { return }
if id == pokemon.id {
imageView.image = UIImage(data: data)
}
nameLabel.text = pokemon.name?.capitalized
configureLabel(label: typeLabel, title: "Type", details: type)
configureLabel(label: pokedexIdLabel, title: "Pokedex Id", details: "\(id)")
configureLabel(label: heightLabel, title: "Height", details: "\(height)")
configureLabel(label: defenseLabel, title: "Defense", details: "\(defense)")
configureLabel(label: weightLabel, title: "Weight", details: "\(weight)")
configureLabel(label: attackLabel, title: "Base Attack", details: "\(attack)")
}
}
let skillLabel: UILabel = {
let label = UILabel()
return label
}()
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
return iv
}()
. . .
}
infoView.configureLabel is this:
func configureLabel(label: UILabel, title: String, details: String) {
let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(title): ", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: Colors.softRed!]))
attributedText.append(NSAttributedString(string: "\(details)", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.gray]))
label.attributedText = attributedText
}
EDIT 3: Structures design
struct Pokemon: Codable {
var results: [Species]?
var abilities: [Ability]?
var id, attack, defense: Int?
var name, type: String?
...
}
struct Ability: Codable {
let ability: Species?
}
struct Species: Codable {
let name: String?
let url: String?
}
Jump to the Edit2 paragraph for the final answer!
Initial Answer:
I looks like you UI does not get updated after the controller fetches all the data.
Since all of you UI configuration code is inside the var pokemon / didSet, it's a good idea to extract it to a separate method.
private func updateView(with pokemon: Pokemon?, details: String?) {
guard let id = pokemon?.id, let data = pokemon?.image else { return }
navigationItem.title = pokemon?.name?.capitalized
infoLabel.text = pokemon?.description
infoView.pokemon = pokemon
if id == pokemon?.id {
imageView.image = UIImage(data: data)
infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: details ?? "")
}
}
and now you can easily call in the the didSet
var pokemon: Pokemon? {
didSet { updateView(with: pokemon, details: allNames) }
}
and fetchPokemons completion aswell
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
guard var skillName = names as? String else { return }
self.pokemon?.skillName = skillName
self.allNames = skillName
print(self.allNames)
DispatchQueue.main.async {
self.updateView(with: self.pokemon, details: self.allNames)
}
}
}
It's super important to do any UI setup on the main queue.
Edit:
The fetch function may be causing the problems! you are calling handler multiple times:
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
DispatchQueue.main.async {
self.pokemon? = poke
guard let skills = poke.abilities else { return }
let names = skills.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
handler(names)
}
}
}
Edit2:
After looking at your codebase there are a couple of things you need to change:
1. fetchPokemons implementation
the handler of controller.service.fetchPokes gets called for every pokemon so we need to check if the fetched one is the current (self.pokemon) and then call the handler with properly formated skills.
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
guard poke.id == self.pokemon?.id else { return }
self.pokemon? = poke
let names = poke.abilities?.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
handler(names ?? "-")
}
}
2. update viewDidLoad()
now simply pass the names value to the label.
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
self.pokemon?.skillName = names
self.infoView.configureLabel(label: self.infoView.skillLabel, title: "Skills", details: names)
}
}
3. Refactor var pokemon: Pokemon? didSet observer
var pokemon: Pokemon? {
didSet {
guard let pokemon = pokemon, let data = pokemon.image else { return }
navigationItem.title = pokemon.name?.capitalized
infoLabel.text = pokemon.description!
infoView.pokemon = pokemon
imageView.image = UIImage(data: data)
}
}
I am using this line below :
self.present(activityViewController, animated: true, completion: nil)
And I am getting an error of - Use of unresolved identifier 'self'. Any ideas about how to resolve this? To me it looks as it it is subordinate to the class, but clearly doing something wrong. Any help would be appreciated.
import UIKit
import CoreData
class CoreDataViewController: UIViewController {
#IBOutlet weak var CoreDataView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items:[Checkins]?
var btnnames = [""]
override func viewDidLoad() {
super.viewDidLoad()
// CoreDataView.dataSource = self
// CoreDataView.delegate = self
storeTranscription()
// Loads the current data
getTranscriptions()
// fetchCheckins()
let btn1name = btnnames[0]
let btn2name = btnnames[1]
let btn3name = btnnames[2]
let btn4name = btnnames[3]
let btn5name = btnnames[4]
let btn6name = btnnames[5]
// print(btnnames)
print(btn1name, btn2name, btn3name, btn4name, btn5name, btn6name)
}
#IBAction func export(_ sender: Any) {
exportDatabase()
}
#IBOutlet weak var Table_label: UILabel!
}
var CheckinDate: Date? = Date()
var fetchedStatsArray: [NSManagedObject] = []
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func storeTranscription() {
//retrieve the entity that we just created
let entity = NSEntityDescription.entity(forEntityName: "Checkins", in: context)
let transc = NSManagedObject(entity: entity!, insertInto: context) as! Checkins
//set the entity values
transc.who = "Who"
transc.reason = "Reason for visit"
transc.date = CheckinDate
//save the object
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func getTranscriptions () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<Checkins> = Checkins.fetchRequest()
do {
//go get the results
let searchResults = try context.fetch(fetchRequest)
fetchedStatsArray = searchResults as [NSManagedObject]
//I like to check the size of the returned results!
print ("num of results = \(searchResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for trans in searchResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
print("\(trans.value(forKey: "who")!)")
let mdate = trans.value(forKey: "CheckinDate") as! Date
print(mdate)
}
} catch {
print("Error with request: \(error)")
}
}
func exportDatabase() {
let exportString = createExportString()
saveAndExport(exportString: exportString)
}
func saveAndExport(exportString: String) {
let exportFilePath = NSTemporaryDirectory() + "Checkins.csv"
let exportFileURL = NSURL(fileURLWithPath: exportFilePath)
FileManager.default.createFile(atPath: exportFilePath, contents: NSData() as Data, attributes: nil)
//var fileHandleError: NSError? = nil
var fileHandle: FileHandle? = nil
do {
fileHandle = try FileHandle(forWritingTo: exportFileURL as URL)
} catch {
print("Error with fileHandle")
}
if fileHandle != nil {
fileHandle!.seekToEndOfFile()
let csvData = exportString.data(using: String.Encoding.utf8, allowLossyConversion: false)
fileHandle!.write(csvData!)
fileHandle!.closeFile()
let firstActivityItem = NSURL(fileURLWithPath: exportFilePath)
let activityViewController : UIActivityViewController = UIActivityViewController(
activityItems: [firstActivityItem], applicationActivities: nil)
activityViewController.excludedActivityTypes = [
UIActivity.ActivityType.assignToContact,
UIActivity.ActivityType.saveToCameraRoll,
UIActivity.ActivityType.postToFlickr,
UIActivity.ActivityType.postToVimeo,
UIActivity.ActivityType.postToTencentWeibo
]
self.present(activityViewController, animated: true, completion: nil)
}
}
func createExportString() -> String {
var checkinwho: String?
var checkinreason: String?
var export: String = NSLocalizedString("who, reason, date \n", comment: "")
for (index, itemList) in fetchedStatsArray.enumerated() {
if index <= fetchedStatsArray.count - 1 {
checkinwho = Checkins.value(forKey: "who") as! String?
checkinreason = itemList.value(forKey: "reason") as! String?
let Datevar = Checkins.value(forKey: "date") as! Date
let whostring = checkinwho
let reasonstring = checkinreason
let DateSting = "\(Datevar)"
export += "\(whostring!),\(reasonstring!),\(DateSting) \n"
}
}
print("This is what the app will export: \(export)")
return export
}
Remove the } on this line
#IBOutlet weak var Table_label: UILabel!
}
and put another } at the end of this file.
I have a collection view, that has a few songs, and upon selecting these songs, the app moves into a playerVC where the pause button is and the song image.
If the user selects song a for example, the music starts so "player.play()" is true, and then the user is moved into a playerVC with it's image etc, now say the user leaves this playerVC and wants to return to it to pause the song, the whole song restarts. How can I prevent this from happening? I know I have to implement an If statement to do something is "player.isPlaying" but do not know what to put in it.
This is the class where the audio is initialised
class SoundManager {
public var position = 0
public var songs: [Song] = []
var player: AVAudioPlayer?
func playNavPlayer() {
let song = songs[position]
let urlString = Bundle.main.path(forResource: song.trackName, ofType: "mp3")
do {
try AVAudioSession.sharedInstance().setMode(.default) ///app needs to know what mode we are working on
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let urlString = urlString else { return }
player = try AVAudioPlayer(contentsOf: URL(string: urlString)!)
guard let player = player else {return}
player.volume = 0.5
// player.play()
// player.delegate = self
player.numberOfLoops = -1
} catch {
print ("Error")
}
}
}
Updated Singleton Class
class SoundManager {
private init(){ }
static let shared = SoundManager()
public var position = 0
public var songs: [Song] = []
var player: AVAudioPlayer?
var songURL: URL!
func playNavPlayer() {
let song = songs[position]
let urlString = Bundle.main.path(forResource: song.trackName, ofType: "mp3")
do {
try AVAudioSession.sharedInstance().setMode(.default) ///app needs to know what mode we are working on
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let urlString = urlString else { return }
player = try AVAudioPlayer(contentsOf: URL(string: urlString)!)
guard let player = player else {return}
player.volume = 0.5
player.play()
//player.delegate = self
player.numberOfLoops = -1
}
catch {
print ("Error")
}
}
How this class is called
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
//present player
let position = indexPath.item
guard let vc = storyboard?.instantiateViewController(identifier: "player") as? PlayerViewController else {
return
}
func push() {
vc.songs = songs
vc.position = position
vc.soundManager = SoundManager.shared
navigationController?.pushViewController(vc, animated: true)
}
SoundManager.shared.songs = songs
SoundManager.shared.position = position
if SoundManager.shared.player?.isPlaying == true && SoundManager.shared.player?.url (This is where I need to see compare song playing with song clicked) {
push()
}
else {
SoundManager.shared.playNavPlayer()
push()
}
SoundManager.shared.setupRemoteTransportControls()
SoundManager.shared.setupNowPlaying()
//songs
}
Song Declaration
var songs = [Song]()
func configureSongs() {
songs.append(Song(name: "Empire", imageName: "cover1", trackName: "Empire"))
songs.append(Song(name: "FirstSample", imageName: "cover2", trackName: "FirstSample"))
songs.append(Song(name: "TBH", imageName: "cover3", trackName: "TBH"))
songs.append(Song(name: "Trials", imageName: "cover4", trackName: "Trials"))
songs.append(Song(name: "Magic Spells", imageName: "cover5", trackName: "MagicSpells"))
songs.append(Song(name: "Still Goin", imageName: "cover6", trackName: "StillGoin"))
}
Song, is a struct
import Foundation
struct Song {
let name: String
let imageName: String
let trackName: String
}
So the Song struct should conform the Equatable protocol, in order for us to use a function in which we compare two instances of our Song struct, like this
struct Song: Equatable {
let name: String
let imageName: String
let trackName: String
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.trackName == rhs.trackName
}
}
And then when we select a song(item) in didSelect we implement an if function to check wether the player is playing and also to compare the two track names, the one that is playing and the one we just clicked on, then if true we specify what we want to do in the curly brackets.
if SoundManager.shared.player?.isPlaying == true && songs[position] == SoundManager.shared.songs[SoundManager.shared.position] {
//Whatever you want to do
}
My swift code below when loaded places 3 items in the core data entity named "UserName". When the user enters a number into textfield enterT I want the label labelName to display it. So when the user enters 1 the label should display jessica biel because Jesical Biel is the first name entered. Someone stated the suggestion below to solve this problem. I dont know exactly how to do this.I have added a gif below.
Convert the entered number to Int. If this succeeds pass the integer to joke and fetch the record matching the idx attribute.
https://github.com/redrock34/index-fetch
import UIKit
import CoreData
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet var labelName : UILabel!
#IBOutlet var enterT : UITextField!
lazy var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
openDatabse()
fetchData()
enterT.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
guard let index = Int(textField.text!) else {
// display an alert about invalid text
return
}
joke(at: index - 1)
}
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
labelName.text = user.username
}
} catch {
print("Could not fetch \(error) ")
}
}
func openDatabse()
{
let names = ["kim kardashian", "jessica biel", "Hailey Rienhart"]
for i in 0..<names.count {
let newUser = Users(context: context)
newUser.username = names[i]
newUser.idx = Int32(i + 1)
}
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed", error)
}
}
func fetchData()
{
print("Fetching Data..")
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
let userName = data.value(forKey: "username") as! String
print("User Name is : "+userName)
}
} catch {
print("Fetching data Failed")
}
}}
Of course you have to assign values to the idx attribute and you have to assign the result of the fetch to the label.
First replace
let appDelegate = UIApplication.shared.delegate as! AppDelegate //Singlton instanc
var context:NSManagedObjectContext!
with
lazy var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
Then replace both openDatabse and saveData with
func openDatabse()
{
let names = ["kim kardashian", "jessica biel", "Hailey Rienhart"]
for i in 0..<names.count {
let newUser = Users(context: context)
newUser.name = names[i]
newUser.idx = Int32(i + 1)
}
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed", error)
}
}
Finally add a line in joke to display the value
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
labelName.text = user.username
}
} catch {
print("Could not fetch \(error) ")
}
}
It creates the records and assigns the proper indexes. Then entering a number in the text field should work.
But – once again – on each launch of the app the 3 records are inserted again with the same names and indexes. Be aware of that!
I am doing a project in Swift 3 - xcode 8, and I am trying to use core data to save and show some images in a data base table "users".
This image is the user photo in his profile.
Now I've managed to save strings and showing them from core data but I am having problems in working this out with images.
This is what I have so far:
Adding USERS into core data
func addUser() {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
let newUser = NSEntityDescription.insertNewObject(forEntityName: "Users", into: context)
if (firstName.text == "" && lastName.text == "" && contact.text == "" && email.text == "") { //if we have a user profile delete it
deleteUser()
} else { // add a new user profile
newUser.setValue(firstName.text, forKey: "firstName")
newUser.setValue(lastName.text, forKey: "lastName")
newUser.setValue(contact.text, forKey: "contact")
newUser.setValue(email.text, forKey: "email")
//newUser.setValue(imageView.image, forKey: "photo")
//let imgUrl = UIImagePickerControllerReferenceURL as! NSURL
let img = UIImage(named: "f.png")
let imgData = UIImageJPEGRepresentation(img!, 1)
newUser.setValue(imgData, forKey: "photo")
print ("Data added in Users")
}
do {
try context.save()
//print("saved!!!")
Alert.show(title: "Success", message: "Profile Saved", vc: self)
} catch {
// print ("Error")
Alert.show(title: "Error", message: "Profile not Saved", vc: self)
}
}
Showing Users from core data
func showUser() {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
if results.count > 0 {
print("Profile: Data Found:")
for result in results as! [NSManagedObject] {
if let firstNameinData = result.value(forKey: "firstName") as? String{
firstName.text = firstNameinData
print(firstNameinData)
}
if let lastNameinData = result.value(forKey: "lastName") as? String{
lastName.text = lastNameinData
print(lastNameinData)
}
if let contactinData = result.value(forKey: "contact") as? String{
contact.text = contactinData
print(contactinData)
}
if let emailinData = result.value(forKey: "email") as? String{
email.text = emailinData
print(emailinData)
}
if let photoinData = result.value(forKey: "photo") as? UIImage{
imageView.image = photoinData
}
}
} else { // if there is not a user profile
firstName.text = ""
lastName.text = ""
contact.text = ""
email.text = ""
print("Profile : No data found")
}
//print("Loaded!!!")
} catch {
print ("Error Loading")
}
}
I cannot show the image I have saved.
Do you have any tips?
EDIT: Xcode gives me this message "Connection to assetsd was interrupted or assetsd died"
The property photo of Users is (NS)Data, as you do there, converting the
UIImage into NSData.
let img = UIImage(named: "f.png")
let imgData = UIImageJPEGRepresentation(img!, 1)
newUser.setValue(imgData, forKey: "photo")
While when you retrieve the info, you are doing like photo was a UIImage object:
if let photoinData = result.value(forKey: "photo") as? UIImage{
imageView.image = photoinData
}
This is not logical according to previous lines. It should be something like that:
if let imageData = result.value(forKey: "photo") as? NSData {
if let image = UIImage(data:imageData) as? UIImage {
imageView.image = image
}
}
Note: I don't speak Swift, so the proposed code may not compile, but you should get the idea of what's wrong and what's need to be done.
Larme has it almost spot on, but instead of this:
if let image = UIImage(data:imageData) as? UIImage
do this:
if let image = UIImage(data: imageData as Data)
Hope i helps you. work fine for me
var results :[Any] = []
let image = UIImage(named: "image.png")
//this is the line that appears to be wrong
let imageData = UIImagePNGRepresentation(image!) as NSData?
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
// 1
let managedContext =
appDelegate.persistentContainer.viewContext
// 2
let entity =
NSEntityDescription.entity(forEntityName: "Image",
in: managedContext)!
let person = NSManagedObject(entity: entity,
insertInto: managedContext)
// 3
person.setValue(imageData, forKeyPath: "name")
// 4
do {
try managedContext.save()
results.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
Hope this will help you. First I too had a great confusing of storing the image in core Data.
This is used to save the image in coreData
First create Nsmanaged Object Class
class Item: NSManagedObject {
}
Declare the image as NSData
import CoreData
extension Item {
#NSManaged var image: NSData?
#NSManaged var name: String?
#NSManaged var email: String?
}
Now go the View Controller you want to save the image .
class newViewController: UIViewController ,UIImagePickerControllerDelegate,UINavigationControllerDelegate{
var item : Item? = nil
var imagePicker = UIImagePickerController()
var PassImages = UIImage()
#IBOutlet var name: UITextField!
#IBOutlet var email: UITextField!
#IBOutlet var photoclick: UIButton!
var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBAction func clickaction(_ sender: Any) {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary){
print("Button capture")
let picker = UIImagePickerController()
picker.allowsEditing = true
picker.sourceType = .photoLibrary
picker.delegate = self //Don't forget this line!
self.present(picker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImage: UIImage?
if let editedImage = info[.editedImage] as? UIImage {
selectedImage = editedImage
self.image.image = selectedImage!
picker.dismiss(animated: true, completion: nil)
} else if let originalImage = info[.originalImage] as? UIImage {
selectedImage = originalImage
self.image.image = selectedImage!
picker.dismiss(animated: true, completion: nil)
}
}
func imagePickerControllerDidCancel(picker: UIImagePickerController!) {
self.dismiss(animated: true, completion: nil)
}
#IBOutlet var image: UIImageView!=nil
#IBAction func submit(_ sender: Any) {
if name.text != "" && email.text != ""
{
let entityDescription = NSEntityDescription.entity(forEntityName: "Table", in: context)
let item = Item(entity: entityDescription!, insertInto: context)
item.name = name.text
item.email = email.text
item.image = image.image!.pngData()! as NSData
do {
try context.save()
print("saved this moc")
} catch {
return
}
let UserDetailsVc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.navigationController?.pushViewController(UserDetailsVc, animated: true)
}
else
{
print("mail check")
let alertController1 = UIAlertController (title: "Fill Email id", message: "Enter valid email", preferredStyle: UIAlertController.Style.alert)
alertController1.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController1, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
if item != nil {
name.text = item?.name
email.text = item?.email
image.image = UIImage(data: (item?.image)! as Data)
}
}
This controller is used to fetch everything
class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate,NSFetchedResultsControllerDelegate{
var userarray: [Table] = []
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userarray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let name = userarray[indexPath.row]
cell.username.text = name.name
cell.showImage?.image = UIImage(data: (name.image)!)
return cell
}
#IBOutlet var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
override func viewWillAppear(_ animated: Bool) {
fetchData()
}
func fetchData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
userarray = try context.fetch(Table.fetchRequest())
print(userarray,"user")
}catch{
print("error")
}
}
}