Ambiguous use of Subscript Swift - xcode

I have a problem in fetching a String of a Dictionary inside NSUserdefaults, this is my code. I dont know what seem to be the problem:
static func getItemInUserDefaultsDictionary(key: String,dictionaryName: String) -> String {
return defaults.objectForKey(key)![dictionaryName] as? String ?? ""
}

The compiler does not know the proper type of objectForKey because it returns AnyObject
You have to cast the type:
static func getItemInUserDefaultsDictionary(key: String,dictionaryName: String) -> String {
guard let dictionary = defaults.objectForKey(key) as? [String:AnyObject] else { return "" }
return dictionary[dictionaryName] as? String ?? ""
}

try this
static func getItemInUserDefaultsDictionary(key: String,dictionaryName: String) -> String
{
return (defaults.objectForKey("key")![dictionaryName] as? String)!
}

Related

Storing/retriving a Codable Dictionary of structs in UserDefaults doesn't work for me

Swift (v 5/5.1) newbie here, having a hard time with Codables...hoping to get some advise from the experts here.
Okay, I have a simple dictionary from struct where the key is a string. I want to store the dictionary in UserDefaults (and later retrieve). There are some quite similar questions here, but these are mainly addressing nested struct's.
First attempt (error handling removed for simplicity):
public struct PriceStruct:Codable {
var myPrice: Double
var myTime: TimeInterval
var selected: Bool
var direction: Int
var myHigh, myLow: Double
enum CodingKeys: String, CodingKey {
case myPrice = "myPrice"
case myTime = "myTime"
case selected = "selected"
case direction = "direction"
case myHigh = "myHigh"
case myLow = "myLow"
}
}
var myPrices: [String: PriceStruct] = [:]
// [fill myPrices with some data...]
func savePrices() {
// error: Attempt to set a non-property-list object
UserDefaults.standard.set(myPrices, forKey: "prices")
}
func loadPrices() {
// obviously this doesn't work either
let myPrices = UserDefaults.standard.data(forKey: "prices")
}
While I assumed from the documentation, that UserDefaults is capable of storing dictionaries, it doesn't - at least for me.
Next thing I tried was using JSONEncoder like this:
// this time with prior JSON encoding
func savePrices() {
// this works
let json = try! JSONEncoder().encode(myPrices)
UserDefaults.standard.set(json as Data, forKey: "prices")
}
func loadPrices() {
// this doesn't work
let json = UserDefaults.standard.data(forKey: "prices")
let decoder = JSONDecoder()
let decoded = try! decoder.decode(PriceStruct.self, from json!)
}
Unfortunately I'm getting an error when trying to load data back from UserDefaults:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "myPrice", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"myPrice\", intValue: nil) (\"myPrice\").", underlyingError: nil))
Other variants I tried is converting the encoded JSON to an UTF8 encoded string and storing/retrieving this one:
func savePrices() {
// this works too
let json = try! JSONEncoder().encode(myPrices)
UserDefaults.standard.set(String(data: json, encoding: .utf8), forKey: "prices")
}
func loadPrices() {
// and this doesn't work either
let json = UserDefaults.standard.string(forKey: "prices")!.data(using: .utf8)
}
So, from the error raised, CodingKeys seems to be the root of the problem. I tried to switch over using NSKeyedArchiver and NSKeyedUnarchiver` with no success.
I'm really wondering if there is a simple/universal solution to save/load a Dictionary in UserDefaults?
All your comments and suggestions are appreciated. Thanks!
I tried with the below code in my project that will work for me.
User Model
public protocol UserModel: Codable, PrimaryKey {
var id: String { get }
var firstName: String? { get }
var lastName: String? { get }
var userName: String? { get }
var emails: [String] { get }
}
public struct User: UserModel {
public let id: String
public let firstName: String?
public let lastName: String?
public let userName: String?
public let emails: [String]
public enum CodingKeys: String, CodingKey {
case id = "Id"
case firstName = "FirstName"
case lastName = "LastName"
case userName = "UserName"
case emails = "Emails"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.id = try container.decode(String.self, forKey: .id)
self.firstName = try container.decodeIfPresent(String.self, forKey: .firstName)
self.lastName = try container.decodeIfPresent(String.self, forKey: .lastName)
self.userName = try container.decodeIfPresent(String.self, forKey: .userName)
self.emails = try container.decodeIfPresent([String].self, forKey: .emails) ?? []
}
catch let error {
debugPrint(error)
throw error
}
}
}
I have stored in userDefault using below way
User Data Class
class UserData: NSObject
{
let userDefaultKey = "user_information"
var userData: User?
func getDictionary() -> [String: Data]
{
var dicInfo = [String: Data]()
do
{
let _userData = try JSONEncoder().encode(userData)
dicInfo["userData_default"] = _userData
}catch let error{
print("error while save data in User Default: \(error.localizedDescription)")
}
return dicInfo
}
func saveToDefault()
{
let userDefault = UserDefaults.standard
userDefault.set(getDictionary(), forKey: userDefaultKey)
userDefault.synchronize()
}
func loadFromDefault()
{
let userDefault = UserDefaults.standard
if let dicInfo = userDefault.object(forKey: userDefaultKey) as? [String: Data]
{
update(dicInfo)
}
}
func update(_ dictionaryInfo: [String: Data])
{
do
{
if let _userData_data = dictionaryInfo["userData_default"]
{
if let _userData = try? JSONDecoder().decode(User.self, from: _userData_data) {
userData = _userData
}
}
saveToDefault()
}catch let error{
print("error while load From Default data in User Default: \(error.localizedDescription)")
}
}
}
Hope this will help you.

Swift 4: Cannot convert return expression of type 'Int' to return type '[String : Any]?'

In a prepare(for segue:) I add the id:Int of the selected project into user defaults like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let viewController = segue.destination as? ReceiverVC, let project = sender as? Project {
defaults.set(project.id!, forKey: "currProject")
viewController.project = project
}
}
In my ApiRouter I need to access that value (I think I need it as a string since it is a URL parameter) but am getting
"Cannot convert return expression of type 'Int' to return type
'[String : Any]?'"
with this code
let params: ([String: Any]?) = {
switch self {
case .getAllProjects:
return nil
case .getAllParts:
return nil
case .getProjectParts:
return UserDefaults.standard.integer(forKey: "currProject")
}
}()
I don't know another way to capture the selected row and run back to the api to get the correct records
case .getProjectParts:
return ["your key":UserDefaults.standard.integer(forKey: "currProject")]
}

Xcode 7 Error Code "Cannot convert value type([AnyObject]

Heres a copy of my code where the error is giving me, the error is on the line where it says query.findobjectsInBackgroundWithBlock. The full error message is this: `Cannot convert value type ([AnyObject]!, NSError!) -> Void to expected argument type 'PFQueryArrayResultBlock?'
// Retrieve Messages
func retrieveMessages() {
// Create a new PFQuery
var query:PFQuery = PFQuery(className: "Message")
// Call findobjectsinbackground
query.findObjectsInBackgroundWithBlock {(objects:[AnyObject]!, error:NSError!) -> Void in
// Clear the messagesArray
self.messageArray = [String]()
// Loops through the objects
for messageObject in objects {
// Retrieve the text column value of each PFObject
let messageText:String? = (messageObject as! PFObject)["Text"] as? String
// Assign it into our messagesArray
if messageText != nil {
self.messageArray.append(messageText!)
}
}
// Reload the tableview
self.messageTableView.reloadData()
}
}
The method signature had improved in Swift 2.0 with Parse SDK 1.10.0. Replace [AnyObject]! with [PFObject]?. [PFObject] is an optional because Swift doesn't know if it will exist or not.
func retrieveMessages() {
var query:PFQuery = PFQuery(className: "Message")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
self.messageArray = [String]()
for messageObject in objects {
let messageText:String? = (messageObject as! PFObject)["Text"] as? String
if messageText != nil {
self.messageArray.append(messageText!)
}
}
self.messageTableView.reloadData()
}
}

Xcode 6.3.1 Swift 1.2 library wherekey can't be invoked

Recently note that wherekey on PFQuery does not work. I tried 3 approaches but all failed. I am using parse-library-1.7.2 on Xcode 6.3.
Error for approach1: Cannot invoke 'whereKey' with an argument list of type '(String, AnyObject)'
Error for approach2: 'String?'is not convertible to 'StringLiteralConvertible'
Error for approach3: 'AnyObject?' is not convertible to 'String'
Code is as below. Anyone can help with this please? Thanks in advance.
// Define the query that will provide the data for the table view
class MyController: PFQueryTableViewController {
override func queryForTable() -> PFQuery {
var query = PFQuery(className: "Ticket")
//Approach 1
query.whereKey(sellerIdKey, equalTo: currentPhoneUser["objectId"]!)
//Approach 2
if let sellerId = currentPhoneUser["objectId"] as? String {
query.whereKey(sellerIdKey, equalTo: sellerId)
query.orderByDescending("createdAt")
return query
} else {
fatalError("Can't get Object Id of this user")
}
//Approach 3
if let sellerId = currentPhoneUser["objectId"] as! String {
query.whereKey(sellerIdKey, equalTo: sellerId)
query.orderByDescending("createdAt")
return query
} else {
fatalError("Can't get Object Id of this user")
}
}
}
Turns out to be a swift 1.2 issue.
How to cast AnyObject? to String in Swift 1.2
Below is my final solution that compiles:
override func queryForTable() -> PFQuery {
var query = PFQuery(className: "Ticket")
let objectId = currentPhoneUser?["objectId"] as? String
if let constObjectId = objectId {
query.whereKey(sellerIdKey, equalTo: constObjectId)
} else {
fatalError("Cannot find Object Id of current user")
}
return query
}

Get App Name in Swift

How do I get the application name in Swift?
Googling gave me this:
[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"];
I converted it to Swift; error - method doesn't exist:
NSBundle.mainBundle().infoDictionary.objectForKey("CFBundleName")
This should work:
NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
infoDictionary is declared as a var infoDictionary: [NSObject : AnyObject]! so you have to unwrap it, access it as a Swift dictionary (rather than use objectForKey), and, as the result is an AnyObject, cast it.
Update Swift 3 (Xcode 8 beta 2)
Always better to use constants (and optionals) where possible, too:
Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String
I believe this solution is more elegant. What's more, using object(forInfoDictionaryKey:) is encouraged by Apple:
"Use of this method is preferred over other access methods because it returns the localized value of a key when one is available."
extension Bundle {
var displayName: String? {
return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
}
}
Accessing bundle display name:
if let displayName = Bundle.main.displayName {
print(displayName)
}
I have created a simple extension to get the app name that is shown under the icon on the Home screen.
By default, apps only have CFBundleName set. Some apps, however, set CFBundleDisplayName (The user-visible name of the bundle) to change the title under the app icon. Adding spaces is often the case, e.g. bundle name "ExampleApp" could have bundle display name set to "Example App".
extension Bundle {
// Name of the app - title under the icon.
var displayName: String? {
return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
object(forInfoDictionaryKey: "CFBundleName") as? String
}
}
Usage:
let appName = Bundle.main.displayName
Same answer in Swift 4.2
extension Bundle {
class var applicationName: String {
if let displayName: String = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String {
return displayName
} else if let name: String = Bundle.main.infoDictionary?["CFBundleName"] as? String {
return name
}
return "No Name Found"
}
}
you can use it like below
Bundle.applicationName
OR, An other way would be to avoid static or class method or property but to add to instance level.
extension Bundle {
var applicationName: String {
if let displayName: String = self.infoDictionary?["CFBundleDisplayName"] as? String {
return displayName
} else if let name: String = self.infoDictionary?["CFBundleName"] as? String {
return name
}
return "No Name Found"
}
}
and all it like following
Bundle.main.applicationName
Hope this helps :)
Swift 4
let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
simple way:
let appName = NSBundle.mainBundle().infoDictionary?[kCFBundleNameKey as String] as? String
convenient way:
extension NSBundle {
class func mainInfoDictionary(key: CFString) -> String? {
return self.mainBundle().infoDictionary?[key as String] as? String
}
}
print(NSBundle.mainInfoDictionary(kCFBundleNameKey))
kCFBundleNameKey – Standard Info.plist key, see more in CFBundle
// Returns app's name
public static var appDisplayName: String? {
if let bundleDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
return bundleDisplayName
} else if let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
return bundleName
}
return nil
}
let appDisplayName = Bundle.main.infoDictionary?["CFBundleName"] as? String
It's optional, so put it in if let or guard statement.
All answers that just return CFBundleName will often not return the name the user expects, as if bundles have a CFBundleDisplayName, then this key is displayed by Finder, system frameworks, and most other apps.
Most answers just directly access the info dictionary but info dictionaries can be localized by string files and when accessing them directly, this localization is also ignored and thus again a wrong name may be returned, as Finder will display the localized name.
While CFBundleDisplayName is optional in Info.plist files, CFBundleName actually isn't, but if you forget to add it, nothing will break in your system, so you have a corrupt info dict, yet most users will probably never notice and in that case the code most answers may not return anything meaningful at all.
Here's my solution (Swift 3):
private
func stripFileExtension ( _ filename: String ) -> String {
var components = filename.components(separatedBy: ".")
guard components.count > 1 else { return filename }
components.removeLast()
return components.joined(separator: ".")
}
func nameOfApp ( ) -> String {
let bundle = Bundle.main
if let name = bundle.object(forInfoDictionaryKey: "CFBundleDisplayName")
?? bundle.object(forInfoDictionaryKey: kCFBundleNameKey as String),
let stringName = name as? String
{ return stringName }
let bundleURL = bundle.bundleURL
let filename = bundleURL.lastPathComponent
return stripFileExtension(filename)
}
How is this solution better?
It will check CFBundleDisplayName first and only fall back to CFBundleName if not present.
The object() method always operates on the localized version of the info dictionary, so if a localization exists, it will automatically be used.
If neither CFBundleDisplayName nor CFBundleName exist in the dictionary, the code falls back to just using the bundle filename on disk without the extension (so "My Cool App.app" will be "My Cool App"), this is a fallback so that this function will never return nil.
This one works for me in Swift 4.2
guard let dictionary = Bundle.main.infoDictionary else { return "" }
if let version: String = dictionary["CFBundleDisplayName"] as? String {
return version
} else {
return ""
}
This is what worked for me in Xcode 11.0 and Swift 5
let bundleID = Bundle.main.bundleIdentifier
let bundleInfoDict: NSDictionary = Bundle.main.infoDictionary! as NSDictionary
let appName = bundleInfoDict["CFBundleName"] as! String
print(bundleID!)
print(appName)
This should be more like what you are looking for:
let infoDictionary: NSDictionary = NSBundle.mainBundle().infoDictionary as NSDictionary!
let appName: NSString = infoDictionary.objectForKey("CFBundleName") as NSString
NSLog("Name \(appName)")
There may still be a better way to do this but it at least returns the app name correctly in my very limited testing...
Try this:
extension Bundle {
var displayName: String {
let name = object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
return name ?? object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String
}
}
let bundleInfoDict: NSDictionary = NSBundle.mainBundle().infoDictionary!
let appName = bundleInfoDict["CFBundleName"] as String
This one works perfect for me
let appName = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleDisplayName") as! String
For swift 5, iOS 13*
As mentioned before, it‘s an optional, so put it in a guard statement. I do this using a struct:
struct Model {
struct ProgVariablen{
static var appBundleName:String {
get {guard Bundle.main.infoDictionary != nil else {return ""}
return Bundle.main.infoDictionary!["CFBundleName"] as! String
}//end get
}//end computed property
static var appBundleShortVersion:String {
get {guard Bundle.main.infoDictionary != nil else {return ""}
return Bundle.main.infoDictionary ["CFBundleShortVersionString"] as! String
}//end get
}//end computed property
static var appBundleBuild:String {
get {guard Bundle.main.infoDictionary != nil else {return ""}
return Bundle.main.infoDictionary["CFBundleVersion"] as! String
}//end get
}//end computed property
//initialsieren der Variablen
init(
appBundleName:String,
appBundleShortVersion:String,
appBundleBuild:String,
)
{
// do here nothing for 'let'
// do here nothing for 'computed properties'
// your other ‘var’ are here like:
// ProgVariablen.var1 = var1
}//end init
}//end struct ProgVariablen
}//end struct Model
Usage:
print("Model.ProgVariablen.appBundleName: '\(Model.ProgVariablen.appBundleName)'")
extension NSApplication {
static var name: String {
Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String ?? ProcessInfo.processInfo.processName
}
}
// Bundle+appName.swift
extension Bundle {
var appName: String {
object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
object(forInfoDictionaryKey: "CFBundleName") as? String ??
""
}
}
// Usage (non optional)
let appName = Bundle.main.appName
Try this one,
let bundleID = NSBundle.mainBundle().bundleIdentifier

Resources