I use a general CoreData query method in my project.
func query(table: String, searchPredicate: NSPredicate) -> [AnyObject]
{
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: table)
fetchRequest.predicate = searchPredicate
let results = try! context.fetch(fetchRequest)
return results
}
In Swift 3 this doesn't work. I found this on Apple's web site:
func findAnimals()
{
let request: NSFetchRequest<Animal> = Animal.fetchRequest
do
{
let searchResults = try context.fetch(request)
... use(searchResults) ...
}
catch
{
print("Error with request: \(error)")
}
}
Using the Apple example, how would I pass Animal in to the method as a parameter to make findAnimals more generic?
I haven't tried this but I think something like this would work...
func findCoreDataObjects<T: NSManagedObject>() -> [T] {
let request = T.fetchRequest
do
{
let searchResults = try context.fetch(request)
... use(searchResults) ...
}
catch
{
print("Error with request: \(error)")
}
}
You have to make the entire function generic and so you have to tell it what type T is when calling it.
someObject.findCoreDataObjects<Animal>()
I think that should do the job. Not entirely certain though as I'm new to generics myself :D
How about this.
func query<T: NSManagedObject>(table: String, searchPredicate: NSPredicate) -> [T] {
let context = app.managedObjectContext
let fetchRequest: NSFetchRequest<T> = NSFetchRequest(entityName: table)
fetchRequest.predicate = searchPredicate
let results = try! context.fetch(fetchRequest)
return results
}
Here is the final result that may help someone:
import Foundation
import Cocoa
func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
let entityName = T.description()
let context = app.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
let record = T(entity: entity!, insertInto: context)
return record
}
func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
let recs = allRecords(T.self)
return recs.count
}
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let predicate = search
{
request.predicate = predicate
}
if let sortDescriptors = multiSort
{
request.sortDescriptors = sortDescriptors
}
else if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func deleteRecord(_ object: NSManagedObject)
{
let context = app.managedObjectContext
context.delete(object)
}
func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
let context = app.managedObjectContext
let results = query(T.self, search: search)
for record in results
{
context.delete(record)
}
}
func saveDatabase()
{
let context = app.managedObjectContext
do
{
try context.save()
}
catch
{
print("Error saving database: \(error)")
}
}
Call it with:
let name = "John Appleseed"
let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name
let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %#", name))
for contact in contacts
{
print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}
deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %#", name))
recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")
saveDatabase()
I use that way in my projects:
static func retrieveRecords<T: NSManagedObject>(table: String, sortDescriptorKey: NSSortDescriptor? = nil) -> [T] {
do {
let fetchRequest: NSFetchRequest<T> = NSFetchRequest(entityName: table)
fetchRequest.sortDescriptors = [sortDescriptorKey!]
let results = try context.fetch(fetchRequest)
print("\(results)")
return results
} catch let error {
print("Could not fetch \(error.localizedDescription)")
return []
}
}
And to call it:
personen = retrieveRecords(table: "Person", sortDescriptorKey: NSSortDescriptor(key: #keyPath(Person.nachname), ascending: true, selector: #selector(NSString.localizedCompare)))
Related
Here is my async function class:
class MoviesViewModel: ObservableObject {
#Published var topRated: [Movie] = []
#Published var popular: [Movie] = []
#Published var upcoming: [Movie] = []
func getUpcomingMovies() {
if let movies = getMovies(path: "upcoming") {
DispatchQueue.main.async {
self.upcoming = movies
}
}
}
func getPopularMovies() {
if let movies = getMovies(path: "popular") {
DispatchQueue.main.async {
self.popular = movies
}
}
}
func getTopRatedMovies() {
DispatchQueue.main.async {
if let movies = self.getMovies(path: "top_rated") {
self.topRated = movies
}
}
}
func getMovies(path: String) -> [Movie]? {
var movies: [Movie]?
let urlString = "https://api.themoviedb.org/3/movie/\(path)?api_key=\(apiKey)&language=en-US&page=1"
guard let url = URL(string: urlString) else { return [] }
let session = URLSession.shared
let dataTask = session.dataTask(with: url, completionHandler: { data, _, error in
if error != nil {
print(error)
}
do {
if let safeData = data {
let decodedData = try JSONDecoder().decode(NowPlaying.self, from: safeData)
DispatchQueue.main.async {
movies = decodedData.results
}
}
}
catch {
print(error)
}
})
dataTask.resume()
return movies
}
}
When I printed the movies in getMovies function, I can get movies from api without problem. However, UI does not update itself. I used DispatchQueue.main.async function but it did not solve my problem. What can I do in this situation?
dataTask works asynchronously. Your code returns nil even before the asynchronous task is going to start. You have to use a completion handler as described in Returning data from async call in Swift function.
I highly recommend to use async/await in this case. You get rid of a lot of boilerplate code and you don't need to care about dispatching threads.
#MainActor
class MoviesViewModel: ObservableObject {
#Published var topRated: [Movie] = []
#Published var popular: [Movie] = []
#Published var upcoming: [Movie] = []
func getUpcomingMovies() async throws {
self.upcoming = try await getMovies(path: "upcoming")
}
func getPopularMovies() async throws {
self.popular = try await getMovies(path: "popular")
}
func getTopRatedMovies() async throws {
self.topRated = try await getMovies(path: "top_rated")
}
func getMovies(path: String) async throws -> [Movie] {
let urlString = "https://api.themoviedb.org/3/movie/\(path)?api_key=\(apiKey)&language=en-US&page=1"
guard let url = URL(string: urlString) else { throw URLError(.badURL) }
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(NowPlaying.self, from: data).results
}
}
Here is how I add new entity.
func addCountry(name: String, code: String, flagImageUri: String?, wikiDataId: String) {
let newCountry = CountryEntity(context: container.viewContext)
newCountry.name = name
newCountry.code = code
newCountry.flagImageUri = flagImageUri
newCountry.wikiDataId = wikiDataId
save()
}
Here is my data:
However when I use the add function in my view, I got this error:
CoreData: error: +[CountryEntity entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
And this is my button:
Button(action: {
country.isFaved = !country.isFaved
coreDataModel.addCountry(name: country.name, code: country.code, flagImageUri: country.flagImageUri, wikiDataId: country.wikiDataId)
}) {
Image(systemName: "star.fill")
.foregroundColor(country.isFaved ? .black : .white)
.scaledToFit()
}
This is the whole class. I'm fetching, saving ,adding and deleting all data here. I did everything like the video I watched in youtube.
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "CountryCoreData")
#Published var savedCountries: [CountryEntity] = []
init() {
container.loadPersistentStores(completionHandler: { _, error in
if let error = error {
print("CoreData failed to load: \(error.localizedDescription)")
} else {
print("Successfully loaded")
}
})
}
func fetchCountries() -> [CountryEntity]? {
let request = NSFetchRequest<CountryEntity>(entityName: "CountryEntity")
do {
let fetchedCountries = try container.viewContext.fetch(request)
return fetchedCountries
} catch {
print("Something went wrong while data fetching \(error)")
return nil
}
}
func delete(code: String) {
guard let fetchedCountries = fetchCountries() else { return }
for country in fetchedCountries {
if country.code!.contains(code) {
container.viewContext.delete(country)
save()
}
}
}
func addCountry(name: String, code: String, flagImageUri: String?, wikiDataId: String) {
let newCountry = CountryEntity(context: container.viewContext)
print("OSMAN")
newCountry.name = name
newCountry.code = code
newCountry.flagImageUri = flagImageUri
newCountry.wikiDataId = wikiDataId
save()
}
func save() {
do {
try container.viewContext.save()
fetchCountries()
} catch {
print("Error while saving the data: \(error)")
}
}
}
How can I solve this problem?
I'm registering some data of user in database and after that the API returns others data in JSON usuario, like this:
And i'm trying to get idUsuario, nome and cpf from this JSON and print to see if they are correct, but they don't appear on console!
#IBAction func botaoSalvar(_ sender: Any) {
let nomeUsuario = self.campoUsuario.text;
let cpf = self.campoCPF.text;
let senha = self.campoSenha.text;
let parameters = ["nome": nomeUsuario, "cpf": cpf, "senha": senha, "method": "app-set-usuario"]
let urlPost = "http://easypasse.com.br/gestao/wsCadastrar.php"
guard let url = URL(string: urlPost) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return }
request.httpBody = httpBody
let session = URLSession.shared
session.dataTask(with: request) {
(data, response, error) in
if let data = data {
do {
let dadosJson = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let usuario = json["usuario"] as? [String: Any] {
let idUsuario = usuario["idUsuario"] as? Int
let nome = usuario["nome"] as? String
let cpf = usuario["cpf"] as? Int
print(idUsuario as! Int, nome as! String, cpf as! Int)
}
} catch {
print(error)
}
}
}.resume()
The value for key usuario is an array, please notice the (, dictionary is {. Blame the owner of the service for singular / plural confusion 😉.
This is your code with a few swiftifications (native collection types and no never .mutableContainers):
if let data = data {
do {
if let dadosJson = try JSONSerialization.jsonObject(with: data) as? [String:Any],
let usuarios = dadosJson["usuario"] as? [[String:Any]] {
for usuario in usuarios {
if let nomeUsuario = usuario["nome"] as? String {
print(nomeUsuario)
}
if let idUsuario = usuario["idUsuario"] as? Int { // can also be `String`
print(idUsuario)
}
if let cpf = usuario["cpf"] as? Int { // can also be `String`
print(cpf)
}
}
}
} catch {
print(error)
}
}
I am fetching user's information like his name,phone number and email id from contacts.But it is only showing first contact number.IF a person has more than one contact number,it didnt show that second number.Can someone help?I am using this function
where EVContactProtocol is part of Library
func didChooseContacts(_ contacts: [EVContactProtocol]?) {
var conlist : String = ""
if let cons = contacts {
for con in cons {
if let fullname = con.fullname(),let email1 = con.email , let phoneNumber = con.phone {
conlist += fullname + "\n"
print("Full Name: ",fullname)
print("Email: ",email1)
print("Phone Number: ",phoneNumber)
}
}
self.textView?.text = conlist
} else {
print("I got nothing")
}
let _ = self.navigationController?.popViewController(animated: true)
}
You should try this:
import Contacts
class ViewController: UIViewController
{
lazy var contacts: [CNContact] =
{
let contactStore = CNContactStore()
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactPhoneNumbersKey] as [Any]
// Get all the containers
var allContainers: [CNContainer] = []
do
{
allContainers = try contactStore.containers(matching: nil)
}
catch
{
print("Error fetching containers")
}
var results: [CNContact] = []
// Iterate all containers and append their contacts to our results array
for container in allContainers
{
let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)
do
{
let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
results.append(contentsOf: containerResults)
}
catch
{
print("Error fetching results for container")
}
}
return results
}()
override func viewDidLoad()
{
super.viewDidLoad()
print(contacts[0].givenName)
print(contacts[0].phoneNumbers)
print(contacts[0].emailAddresses)
print(contacts)
}
}
I am trying to use youtube api in ios swift, and following this tutorial
http://www.appcoda.com/youtube-api-ios-tutorial/
HTTP Status Code = 403
Error while loading channel details: nil
I'm using swift 3
var urlString = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=\(textField.text)&type=\(type)&key=\(apiKey)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
// Create a NSURL object based on the above string.
let targetURL = URL(string: urlString)
// Get the results.
performGetRequest(targetURL, completion: { (data, HTTPStatusCode, error) -> Void in
if HTTPStatusCode == 200 && error == nil {
// Convert the JSON data to a dictionary object.
do {
let resultsDict = try JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<String, AnyObject>
// Get all search result items ("items" array).
let items: Array<Dictionary<String, AnyObject>> = resultsDict["items"] as! Array<Dictionary<String, AnyObject>>
// Loop through all search results and keep just the necessary data.
for i in 0 ..< items.count {
let snippetDict = items[i]["snippet"] as! Dictionary<String, AnyObject>
// Gather the proper data depending on whether we're searching for channels or for videos.
if self.segDisplayedContent.selectedSegmentIndex == 0 {
// Keep the channel ID.
self.desiredChannelsArray.append(snippetDict["channelId"] as! String)
}
else {
// Create a new dictionary to store the video details.
var videoDetailsDict = Dictionary<String, AnyObject>()
videoDetailsDict["title"] = snippetDict["title"]
videoDetailsDict["thumbnail"] = ((snippetDict["thumbnails"] as! Dictionary<String, AnyObject>)["default"] as! Dictionary<String, AnyObject>)["url"]
videoDetailsDict["videoID"] = (items[i]["id"] as! Dictionary<String, AnyObject>)["videoId"]
// Append the desiredPlaylistItemDataDict dictionary to the videos array.
self.videosArray.append(videoDetailsDict)
// Reload the tableview.
self.tblVideos.reloadData()
}
}
} catch {
print(error)
}
// Call the getChannelDetails(…) function to fetch the channels.
if self.segDisplayedContent.selectedSegmentIndex == 0 {
self.getChannelDetails(true)
}
}
else {
print("HTTP Status Code = \(HTTPStatusCode)")
print("Error while loading channel videos: \(error)")
}
// Hide the activity indicator.
self.viewWait.isHidden = true
})
return true
}
// MARK: Custom method implementation
func performGetRequest(_ targetURL: URL!, completion: #escaping (_ data: Data?, _ HTTPStatusCode: Int, _ error: NSError?) -> Void) {
// let request = NSMutableURLRequest(url: targetURL)
// request.httpMethod = "GET"
var request = URLRequest(url: targetURL)
request.httpMethod = "GET"
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
/* let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: NSError?) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
completion(data, (response as! HTTPURLResponse).statusCode, error)
})
} as! (Data?, URLResponse?, Error?) -> Void)*/
/* let task = session.dataTask(with: request, completionHandler: ({ (data: Data?, response: URLResponse?, error: NSError?) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
completion(data as Data?, (response as! HTTPURLResponse).statusCode, error)
})
} as! (Data?, URLResponse?, Error?) -> Void))*/
let task = session.dataTask(with: request) { data, response, error in DispatchQueue.main.async { completion(data, (response as! HTTPURLResponse).statusCode, error as? NSError) } }
task.resume()
}
First of all the JSON dictionary representation in Swift 3 is [String:Any] (aka Dictionary<String,Any>)
Second of all in Swift 3 all parameter labels in closures have been removed
func performGetRequest(_ targetURL: URL, completion: #escaping (Data?, Int, NSError?) -> Void) {
Do not use implicit unwrapped optionals for method parameter types. Either use regular optional (?) or non-optional.
Error 403 means Forbidden Access. Make sure you have the correct apiKey from google/youtube developer.
I also used the appcoda youtube api tutorial (which is in Swift 2 I think) and this is a working version of mine for swift 3.
func getVideosForChannelAtIndex() {
let urlString = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(playlistID)&maxResults=\(maxResults)&key=\(apiKey)"
// Create a NSURL object based on the above string.
let targetURL = URL(string: urlString)
// Fetch the playlist from Google.
performGetRequest(targetURL!) { (data, HTTPStatusCode, error) -> Void in
if HTTPStatusCode == 200 && error == nil {
do {
self.videos = []
// Convert the JSON data into a dictionary.
let resultsDict = try JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<AnyHashable, Any>
// Get all playlist items ("items" array).
let items:Array<Dictionary<AnyHashable, Any>> = resultsDict["items"] as! Array<Dictionary<AnyHashable, Any>>
// Use a loop to go through all video items.
// for var i=0; i<items.count; ++i
for i in 0 ..< items.count {
let playlistSnippetDict = (items[i] as Dictionary<AnyHashable, Any>)["snippet"] as! Dictionary<AnyHashable, Any>
let video = Video()
video.title = playlistSnippetDict["title"] as? String
// video.thumbnail =
video.videoId = (playlistSnippetDict["resourceId"] as? Dictionary<AnyHashable, Any>)?["videoId"] as? String
guard let thumbnail = ((playlistSnippetDict["thumbnails"] as? Dictionary<AnyHashable, Any>)?["high"] as? Dictionary<AnyHashable, Any>)?["url"] as? String else {
video.thumbnail = UIImage(named: "Icon1024x1024")
return
}
guard let url:URL? = URL(string: thumbnail), let data:Data? = try? Data(contentsOf: url!) else {
video.thumbnail = UIImage(named: "Icon1024x1024")
return
}
if let dataImage = data {
video.thumbnail = UIImage(data: dataImage)
} else {
video.thumbnail = UIImage(named: "Icon1024x1024")
}
self.videos.append(video)
// Reload the tableview.
self.tblVideos.reloadData()
}
} catch {
print("json error: \(error)")
}
} else {
print("")
print("HTTP Status Code = \(HTTPStatusCode)")
print("")
//Show alertDialog here with Error
print("Error while loading videos: \(error?.localizedDescription)")
let alert = UIAlertView(title: "Oops!", message: error?.localizedDescription, delegate: self, cancelButtonTitle: "OK")
alert.show()
}
// Hide the activity indicator.
self.viewWait.isHidden = true
}
}
This is for the performGetRequest
func performGetRequest(_ targetURL: URL, completion: #escaping (_ data: Data?, _ HTTPStatusCode: Int?, _ error: Error?) -> Void) {
var request = URLRequest(url: targetURL)
request.httpMethod = "GET"
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
let task = session.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
completion(data, (response as? HTTPURLResponse)?.statusCode, error)
})
}
task.resume()
}