Convert 2 similar computed properties to use 1 reusable function in Swift? - swift

I have 2 similar computed properties like x & y
struct Window {
let defaults = UserDefaults.standard
var x: String {
get {
let x = defaults.string(forKey: "x")
return x ?? "3"
}
set {
defaults.set(newValue, forKey: "x")
}
}
var y: String {
get {
let y = defaults.string(forKey: "y")
return y ?? "10"
}
set {
defaults.set(newValue, forKey: "y")
}
}
}
And as x & y are doing the same thing, I want it to make it as a reusable function taking 2 different parameters, the forKey name like "x" & "y" & defaultValue like "2" or "3" in the above example.
I'm new to Swift & can't seem to figure it out if its possible or too easy. How should I do it?

struct Window {
let defaults = UserDefaults.standard
var x: String {
get {
return getValue(for:"x", defaultValue: "3")
}
set {
set(value: newValue, for:"x")
}
}
var y: String {
get {
return getValue(for:"y", defaultValue: "10")
}
set {
set(value: newValue, for:"y")
}
}
func getValue(for key:String, defaultValue:String) -> String {
let value = defaults.string(forKey: key)
return value ?? defaultValue
}
func set(value:String, for key:String) {
defaults.set(value, forKey:key)
}
}

Note that this approach used in your question and posted answers would not work for multiple objects. What you should do is extend UserDefaults and add a static property nested in a Window struct as follow:
extension UserDefaults {
struct Window { }
}
extension UserDefaults.Window {
static var x: CGFloat {
get {
return CGFloat(UserDefaults.standard.object(forKey: "Window.x") as? Double ?? 2.0)
}
set {
UserDefaults.standard.set(Double(newValue), forKey: "Window.x")
}
}
static var y: CGFloat {
get {
return CGFloat(UserDefaults.standard.object(forKey: "Window.y") as? Double ?? 3.0)
}
set {
UserDefaults.standard.set(Double(newValue), forKey: "Window.y")
}
}
}
Playground testing
UserDefaults.Window.x // 2 default value
UserDefaults.Window.x = 10
UserDefaults.Window.x // 10
A better approach (as already mentioned by #Sulthan in comments) would be merging x and y properties in a point (CGPoint) property
extension UserDefaults.Window {
static var point: CGPoint {
get {
guard let data = UserDefaults.standard.data(forKey: "Window.point") else { return CGPoint(x: 2, y: 3) }
return (NSKeyedUnarchiver.unarchiveObject(with: data) as? NSValue)?.cgPointValue ?? CGPoint(x: 2, y: 3)
}
set {
UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: newValue), forKey: "Window.point")
}
}
}
Playground testing
UserDefaults.Window.point // {x 2 y 3} default value
UserDefaults.Window.point = .zero
UserDefaults.Window.point // {x 0 y 0}

Related

Save random alphanumeric string in Userdefaults

i'm new to swift and i have a problem. How i can save randomly generated for each user alphanumeric string in user defaults?
func randomString(of length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var s = ""
for _ in 0 ..< length {
s.append(letters.randomElement()!)
}
return s
}
static var keyS: Bool {
get {
return ((UserDefaults.standard.integer(forKey: randomString(of: 16)) != 0))
}
set {
UserDefaults.standard.set(1, forKey: randomString(of: 16))
}
}
i tried this, but it didn't work. hope somebody can help me
Here "1" is your key for storing the random string. You can set it anything you want. Try this-
func randomString(of length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var s = ""
for _ in 0 ..< length {
s.append(letters.randomElement()!)
}
return s
}
static var keyS: Bool {
get {
return ((UserDefaults.standard.integer(forKey: "1") != 0))
}
set {
UserDefaults.standard.set(randomString(of: 16), forKey: "1")
}
}

How would one implement a bidirectional map in Swift?

I am currently in need of a performant bidirectional map. In Swift, a dictionary can be reversed, however, that will return a tuple of the types it is made of, not a counterpart dictionary.
Is there a library for that or does someone have ideas on how to address this issue?
Thanks
With Swift 4 you could easily make your own using a generic struct:
struct BidiMap<F:Hashable,T:Hashable>
{
private var _forward : [F:T]? = nil
private var _backward : [T:F]? = nil
var forward:[F:T]
{
mutating get
{
_forward = _forward ?? [F:T](uniqueKeysWithValues:_backward?.map{($1,$0)} ?? [] )
return _forward!
}
set { _forward = newValue; _backward = nil }
}
var backward:[T:F]
{
mutating get
{
_backward = _backward ?? [T:F](uniqueKeysWithValues:_forward?.map{($1,$0)} ?? [] )
return _backward!
}
set { _backward = newValue; _forward = nil }
}
init(_ dict:[F:T] = [:])
{ forward = dict }
init(_ values:[(F,T)])
{ forward = [F:T](uniqueKeysWithValues:values) }
subscript(_ key:T) -> F?
{ mutating get { return backward[key] } set{ backward[key] = newValue } }
subscript(_ key:F) -> T?
{ mutating get { return forward[key] } set{ forward[key] = newValue } }
subscript(to key:T) -> F?
{ mutating get { return backward[key] } set{ backward[key] = newValue } }
subscript(from key:F) -> T?
{ mutating get { return forward[key] } set{ forward[key] = newValue } }
var count:Int { return _forward?.count ?? _backward?.count ?? 0 }
}
var bd = BidiMap( [1:"A", 2:"B", 3:"C"] )
bd[1] // "A"
bd["B"] // 2
bd[4] = "D"
bd[to:"D"] // 4
bd[from:4] // "D"
var int2int = BidiMap( [1:2, 5:3] )
int2int[from:1] // 2
int2int[to:3] // 5
[EDIT] improved performance a bit by delaying rebuilding of mirror dictionary until it is actually referenced.

Generate random String without repeating in swift

I want the function to generate random String without repeating.
For example this function maybe will print: ABCC
func randomString(length:Int) -> String {
let charSet = "ABCDEF"
var c = charSet.characters.map { String($0) }
var s:String = ""
for _ in (1...length) {
s.append(c[Int(arc4random()) % c.count])
}
return s
} print(randomString(length: 4))
and i want print random unique string only, E.g : ABCD
import GameplayKit
func randomString(length : Int) -> String {
let charSet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters)
let shuffled = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: charSet) as! [Character]
let array = shuffled.prefix(length)
return String(array)
}
print(randomString(length: 4))
func randomString(length: Int) -> String {
let charSet = "ABCDEF"
var charSetArray = charSet.characters.map { String($0) }
var randArray: [String] = []
while charSetArray.count > 0 {
let i = Int(arc4random_uniform(UInt32(charSetArray.count)))
randArray.append(charSetArray[i])
charSetArray.remove(at: i)
}
var output: String = ""
for i in 0..<length {
output.append(randArray[i])
}
return output
}
How to use:
let randomString = "ABCDEF".random(length: 3)!
The return value is optional because the length might exceed the length of provided string.
Check out the full implementation:
import UIKit
import PlaygroundSupport
extension MutableCollection where Indices.Iterator.Element == Index {
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
guard d != 0 else { continue }
let i = index(firstUnshuffled, offsetBy: d)
swap(&self[firstUnshuffled], &self[i])
}
}
}
extension Sequence {
func shuffled() -> [Iterator.Element] {
var result = Array(self)
result.shuffle()
return result
}
}
extension String {
func random(length: Int) -> String? {
let uniqueCharacters = Array(Set(characters.map({ String($0) })))
guard length <= uniqueCharacters.count else { return nil }
guard length > 0 else { return nil }
return uniqueCharacters[0..<length].shuffled().joined()
}
}

compute a value to a let struct property in init

I have a swift struct named Product which takes a Dictionary in its init method. I want to compute a price value in a local Price struct within my Product. I want this value to be a let constant since it won't ever change, but swift won't allow me to do that without using var, saying that the let constant is not properly initialized.
How can I make my value property inside my Price struct a let constant in this case?
struct Product {
let price: Price
init(dictionary: Dictionary<String, AnyObject>) {
if let tmp = dictionary["price"] as? Dictionary<String, AnyObject> { price = Price(dictionary: tmp) } else { price = Price() }
}
struct Price {
var value = ""
init() {
}
init(dictionary: Dictionary<String, AnyObject>) {
if let xForY = dictionary["xForY"] as? Array<Int> {
if xForY.count == 2 {
value = "\(xForY[0]) for \(xForY[1])"
}
}
if let xForPrice = dictionary["xForPrice"] as? Array<Int> {
if value == "" && xForPrice.count == 2 {
value = "\(xForPrice[0]) / \(xForPrice[1]):-"
}
}
if let reduced = dictionary["reduced"] as? String {
if value == "" {
value = "\(reduced):-"
}
}
}
}
}
You have to rewrite the code so that the compiler gets what you intend to do actually. It is not clever enough to deduce it from the way you coded it.
I would also suggest that you make the initializer for the Price struct failable instead of using an empty string for the value property. As a consequence of that change the price property of the Product struct becomes an optional.
struct Product {
let price: Price?
init(dictionary: Dictionary<String, AnyObject>) {
if let tmp = dictionary["price"] as? Dictionary<String, AnyObject> {
price = Price(dictionary: tmp)
}
else {
price = nil
}
}
struct Price {
let value : String
init?(dictionary: Dictionary<String, AnyObject>) {
if let xForY = dictionary["xForY"] as? Array<Int> where xForY.count == 2 {
value = "\(xForY[0]) for \(xForY[1])"
}
else if let xForPrice = dictionary["xForPrice"] as? Array<Int> where xForPrice.count == 2 {
value = "\(xForPrice[0]) / \(xForPrice[1]):-"
}
else if let reduced = dictionary["reduced"] as? String {
value = "\(reduced):-"
}
else {
return nil
}
}
}
}
The trouble is that (1) you're assigning to value in the declaration, (2) you're not assigning a value in init(), and (3) you're referencing and reassigning value in init([String: AnyObject]). You can only assign a value to a constant once and can only reference its value after its been assigned to.
To fix the issue, you can either make your property publicly readonly:
private(set) var value: String = ""
Or you can use a second variable inside your init:
struct Price {
let value: String
init() {
self.value = ""
}
init(dictionary: Dictionary<String, AnyObject>) {
var v: String = ""
if let xForY = dictionary["xForY"] as? Array<Int> {
if xForY.count == 2 {
v = "\(xForY[0]) for \(xForY[1])"
}
}
if let xForPrice = dictionary["xForPrice"] as? Array<Int> {
if v == "" && xForPrice.count == 2 {
v = "\(xForPrice[0]) / \(xForPrice[1]):-"
}
}
if let reduced = dictionary["reduced"] as? String {
if v == "" {
v = "\(reduced):-"
}
}
self.value = v
}
}

How can I sort array with two kind of objects in Swift?

This is my method when I sort elements by position property. Both DBSet and DBItem have that property.
#objc(DBCategory)
class DBCategory: NSManagedObject {
#NSManaged var identifier: String
#NSManaged var items: Set<DBItem>
#NSManaged var sets: Set<DBSet>
}
And this is how I use it
private var elements = [AnyObject]()
private func prepareElements() {
elements.removeAll(keepCapacity: false)
if let items = currentCategory?.items {
for item in items {
elements.append(item)
}
}
if let sets = currentCategory?.sets {
for set in sets {
elements.append(set)
}
}
elements.sort {
var previousPosition = 0
var currentPosition = 0
if let set = $0 as? DBSet {
previousPosition = Int(set.position)
}
if let item = $0 as? DBItem {
previousPosition = Int(item.position)
}
if let set = $1 as? DBSet {
currentPosition = Int(set.position)
}
if let item = $1 as? DBItem {
currentPosition = Int(item.position)
}
return previousPosition < currentPosition
}
}
position is type of Int16
How can I simplify that?
Create a protocol that defines your position property. Let's call it HasPosition. Declare an array of [HasPosition]. You can then sort the array directly with any of the standard sorting functions.
If .position returns the same type in both cases, and that type is comparable, you can simplify that to the following:
elements.sort {
let l = ($0 as? DBSet)?.position ?? ($0 as? DBItem)?.position
let r = ($1 as? DBSet)?.position ?? ($1 as? DBItem)?.position
return l < r
}
The key to this working is that there is a version of < that works for optionals, where nil is defined as less than any non-nil value. So values in the array not of one of the two types will be sorted to the beginning.
When you have the array as [AnyObject] and the position is Int16, this should works:
elements.sort {
($0.position as Int16?) < ($1.position as Int16?)
}
This works because AnyObject has any #objc properties as Optional.
Demo:
class DBItem:NSObject {
var position:Int16
init(position: Int16) { self.position = position }
override var description:String { return "DBItem(\(position))" }
}
class DBSet:NSObject {
var position:Int16
init(position: Int16) { self.position = position }
override var description:String { return "DBSet(\(position))" }
}
var elements:[AnyObject] = [
DBItem(position: 5),
DBSet(position: 2),
DBItem(position: 42),
DBSet(position: 62),
DBSet(position: 21),
DBItem(position: 6),
DBSet(position: 36),
DBItem(position: 24),
]
elements.sort {
($0.position as Int16?) < ($1.position as Int16?)
}
println(elements)
// -> [DBSet(2), DBItem(5), DBItem(6), DBSet(21), DBItem(24), DBSet(36), DBItem(42), DBSet(62)]
And your prepareElements() can be simplified as:
private func prepareElements() {
elements = []
if let ct = currentCategory {
elements += Array(ct.items) as [AnyObject]
elements += Array(ct.sets) as [AnyObject]
elements.sort {
($0.position as Int16?) < ($1.position as Int16?)
}
}
}