swift - sort an array of objects by their optional boolean property without force unwrapping - ios

I can sort this array of store objects by their 'flagship' boolean property, but how can I safely unwrap the 'flagship' property first?
let flagshipStores = self.stores.sort {
$0.flagship! && !$1.flagship!
}

let flagshipStores = self.stores.sort {
guard let flagship0 = $0.flagship, let flagship1 = $1.flagship else { return false }
return flagship0 && !flagship1
}

One more approach: turn the Bool? into an Int, then compare the Ints. You get to specify how a nil value compares to non-nil values.
For instance, this sorts nil values before both false and true:
stores.sort { Int($0.flagship ?? -1) < Int($1.flagship ?? -1) }
This sorts nil values after both false and true:
stores.sort { Int($0.flagship ?? 2) < Int($1.flagship ?? 2) }
You can use the same pattern to make nil compare the same as true or the same as false. It's up to you.

Here's another approach.
You can use flatMap which will remove nil objects and unwrap those that are present. Then, the force unwrap will be safe to sort:
let flagshipStores = stores.flatMap({ return $0.flagship ? $0 : nil }).sort {
$0.flagship! && !$1.flagship!
}
This will remove stores with a nil flagship from the array.

How about:
$0.flagship == true && $1.flagship != true
The left side will succeed if the value is not nil and is true, the right side will succeed if the value is either nil or false.

As mr.Fixit pointed out on a comment, the accepted answer doesn't fully work because it doesn't take care of nils. Here is the correct answer with an extra string sample.
SWIFT 4
for a boolean sorting
let flagshipStores = self.stores.sorted(by: {
guard let flagship0 = $0.flagship, let flagship1 = $1.flagship else {
if $0.flagship == nil && $1.flagship == nil || $0.flagship != nil && $1.flagship == nil{
return true
}
else {
return false
}
}
return ($0.flagship == $1.flagship || $0.flagship == true && $1.flagship == false ? true : false)
})
for strings comparison sorting
let stores = self.stores.sorted(by: {
guard let store0 = $0.store, let store1 = $1.store else {
if $0.store == nil && $1.store == nil || $0.store != nil && $1.store == nil{
return true
}
else {
return false
}
}
return ( ($0.store)?.localizedStandardCompare($1.store!) == ComparisonResult.orderedAscending )
})

To filter nil values just use compactMap before sort
let flagshipStores = self.stores.compactMap { return $0.flagship }.sorted {
$0 && !$1
}

You could use this function to compare the Optional values without the need to unwrap.
func sortOptionalValues<T: Comparable>(lhs: T?, rhs: T?) -> Bool? {
switch (lhs != nil, rhs != nil) {
case (false, false):
return nil
case (true, false):
return true
case (false, true):
return false
case (true, true):
guard let lhs = lhs, let rhs = rhs else { return nil }
return lhs < rhs
}
}

Related

How to fix conditional binding have optional type not 'Bool'?

how can I solve this problem?
Ive been getting the same error in 10 different places, I have been running tests on it and can't seem to figure this
thanks in advance for any help that you guys provide it really means a lot to me
Initializer for conditional binding must have Optional type, not 'Bool'
extension HomeController: FiltersViewControllerDelegate{
func query(withCategory jewelry: Bool, shoe: Bool, hat: Bool, apearel: Bool, gear: Bool) -> Query {
if jewelry == false && shoe == false && hat == false && apearel == false && gear == false {
stackViewHeightConstraint.constant = 0
activeFiltersStackView.isHidden = true
} else {
stackViewHeightConstraint.constant = 44
activeFiltersStackView.isHidden = false
}
var filtered = baseQuery
// Sort and Filter data
if let jewelry = jewelry, !jewelry.isEmpty { //Error
filtered = filtered.whereField("category", isEqualTo: jewelry)
}
//......more Filters....\\
if let gear = gear, !gear.isEmpty { //Error
filtered = filtered.whereField("category", isEqualTo: gear)
}
return filtered
}
func controller(_ controller: FilterViewController,
didSelectCategory jewelry: Bool,
shoe: Bool,
hat: Bool,
apearel: Bool,
gear: Bool) {
if jewelry == false && shoe == false && hat == false && apearel == false && gear == false {
stackViewHeightConstraint.constant = 0
activeFiltersStackView.isHidden = true
} else {
stackViewHeightConstraint.constant = 44
activeFiltersStackView.isHidden = false
}
let filtered = query(withCategory: jewelry, shoe: shoe, hat: hat, apearel: apearel, gear: gear)
if let jewelry = jewelry, ! jewelry.isEmpty { //Error
jewelryFilterLbl.text = "Jewelry"
jewelryFilterLbl.isHidden = false
} else {
jewelryFilterLbl.isHidden = true
}
//......more Filters....\\
if let gear = gear, !gear.isEmpty { //Error
gearFilterLbl.text = "gear"
gearFilterLbl.isHidden = false
} else {
gearFilterLbl.isHidden = true
}
query = filtered
}
}
Remove .isEmpty check it's not a property of a Bool
if jewelry { //Error
filtered = filtered.whereField("category", isEqualTo: jewelry)
}
//......more Filters....\\
if gear { //Error
filtered = filtered.whereField("category", isEqualTo: gear)
}
You're using if let binding on variables that are not optionals.
For example, your jewel variable is a Bool, not a Bool?. Using optional binding doesn’t make any sense.
if let jewelry = jewelry, ! jewelry.isEmpty { // Jewelry isn't an Optional!!!
jewelryFilterLbl.text = "Jewelry"
jewelryFilterLbl.isHidden = false
} else {
jewelryFilterLbl.isHidden = true
}
Plus, as other users have stated, Bool variables don't have .isEmpty method. Revise your logic and your code, it doesn't work at all.

Swift equivalent of this ActionScript function

I translated this tutorial on BSP into swift. In the tutorial there's this ActionScript function.
public function getRoom():Rectangle
{
// iterate all the way through these leafs to find a room, if one exists.
if (room != null)
return room;
else
{
var lRoom:Rectangle;
var rRoom:Rectangle;
if (leftChild != null)
{
lRoom = leftChild.getRoom();
}
if (rightChild != null)
{
rRoom = rightChild.getRoom();
}
if (lRoom == null && rRoom == null)
return null;
else if (rRoom == null)
return lRoom;
else if (lRoom == null)
return rRoom;
else if (FlxG.random() > .5)
return lRoom;
else
return rRoom;
}
}
I translated this function into Swift (to the best of my ability) I must have written it wrong because the function is returning a nil value when it shouldn't be.
My version in Swift:
// left/right child are initialized as follows:
// leftChild:Room?
// rightChild:Room?
public func getRoom() -> Room? {
if room != nil {
return room
} else {
var lRoom:Room?
var rRoom:Room?
if leftChild != nil {
lRoom = leftChild!.getRoom()!
}
if rightChild != nil {
rRoom = rightChild!.getRoom()!
}
if lRoom == nil && rRoom == nil {
return nil
} else if rRoom == nil {
return lRoom
} else if lRoom == nil {
return rRoom
} else if Double.random(in: 0..<1.0) > 0.5 {
return lRoom
} else {
return rRoom
}
}
}
Where Room is a basic class I made to help me handle the rooms.
class Room {
var x1:Int
var x2:Int
var y1:Int
var y2:Int
var center:CGPoint
init(X: Int, Y: Int, W: Int, H: Int) {
x1 = X
x2 = X + W
y1 = Y
y2 = Y + H
center = CGPoint(x: (x1 + x2) / 2, y: (y1 + y2) / 2)
}
}
I'm getting a nil value when I shouldn't be. I think I translated the function wrong. Rectangle would be CGRect in Swift but I replaced it with my Room class in other places in the code, so I know it'll work with the Room class here.
How would this function be written in Swift?
Your problem is that you are force unwrapping the result of getRoom - This function returns an optional and can legitimately return nil when your traversal hits a leaf node. Force unwrapping nil results in a crash
By using conditional unwrapping correctly you can not only make your code more readable, you can eliminate the crash.
public func getRoom() -> Room? {
if let _ = room {
return room
}
let lRoom = leftChild?.getRoom()
let rRoom = rightChild?.getRoom()
switch (lRoom != nil, rRoom != nil) {
case (false,false):
return nil
case (true,false):
return lRoom
case (false,true):
return rRoom
case (true,true):
return arc4random_uniform(2) == 1 ? lRoom: rRoom
}
}

What does mobile network code of 65535 mean with CTTelephonyNetworkInfo().subscriberCellularProvider?.mobileNetworkCode?

I'm looking at some code somebody else has written which has no documentation, why is this code making a comparison with 65535?
class func canMakePhoneCall() -> Bool
{
guard let URL = URL(string: "tel://") else {
return false
}
let canOpenURL = UIApplication.shared.canOpenURL(URL)
if canOpenURL == false
{
return false
}
let mobileNetworkCode = CTTelephonyNetworkInfo().subscriberCellularProvider?.mobileNetworkCode
let isInvalidNetworkCode = mobileNetworkCode == nil
|| mobileNetworkCode?.characters.count == 0
|| mobileNetworkCode == "65535"
return isInvalidNetworkCode == false
}
According to an answer here, this could be an indication of removed SIM card, or in general inability to make a call at the moment.

How to get the row index of 2D Array?

I am new to Swift and I am using a 2D array for comparing and I need to get the row index once the condition is true but I got an error state that cannot invoke index with an argument list of type (of:Array<Float>)
My Code:
var entryCoordinate: [[Float]] = [[130.6,61.0],[167.5,61.0],[204.5,61.0],[243.6,61.0],[281.16,61.0],[315.3,61.0]]
for indexs in entryCoordinate
{
if indexs[0] == startPathPointX && indexs[1] == startPathPointY
{
let pathStartElement = indexs.index(of: indexs)
print(pathStartElement)
}
if indexs[0] == endPathPointX && indexs[1] == endPathPointY
{
let pathEndElement = indexs.index(of: indexs)
print(pathEndElement)
}
}
From your code with startPathPointY and endPathPointY you need to compare the second object from 2D array but you keep comparing the first one and you con use index(where:) with your array like this to get the pathStartElement and pathEndElement.
if let pathStartElement = entryCoordinate.index(where: { $0[0] == startPathPointX && $0[1] == startPathPointY }) {
print(pathStartElement)
}
if let pathEndElement = entryCoordinate.index(where: { $0[0] == endPathPointX && $0[1] == endPathPointY }) {
print(pathEndElement)
}
Try this code:
let myIndexPathStartElement = self.arrImagetData.index(where: { $0[0] == startPathPointX && $0[1] == startPathPointY })
let myIndexPathEndElement = self.arrImagetData.index(where: { $0[0] == endPathPointX && $0[1] == endPathPointY })

Disable Alphabetic Characters in a Text Field (Part of my code will not execute)

To prevent the user from inputting more than one decimal into the text field and to prevent the user from inputting alphabetical characters into the text field I have tried the code below. The problem is I don't know how to check them both within the function and have two If statements which means the second one won't run as it never gets executed. If I take the code that checks number of decimals out and leave the character checker code it works perfectly. How can I get them both working though?
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimalSeparator = textField.text?.rangeOfString(".")
let replacementTextHasDecimalSeparator = string.rangeOfString(".")
let charactersNotAllowed = NSCharacterSet.letterCharacterSet()
let replacementTextHasLetter = string.rangeOfCharacterFromSet(charactersNotAllowed)
if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil {
return false
} else {
return true
}
if replacementTextHasLetter != nil {
return false
} else {
return true
}
}
For swift 4 users you could delete the if replacementTextHasLetter != nil condition thats nested in the first condition along with the else condition and the code would work as well:
let existingTextHasDecimalSeparator = textField.text?.range( of: ".")
let replacementTextHasDecimalSeparator = string.range( of: ".")
let charactersNotAllowed = NSCharacterSet.letters
let replacementTextHasLetter = string.rangeOfCharacter(from: charactersNotAllowed)
if existingTextHasDecimalSeparator != nil, replacementTextHasDecimalSeparator != nil{
return false
}
if replacementTextHasLetter != nil{
return false
}
return true
}
The reason your function does not work is that it makes its decision early for both the positive and the negative case.
Defer returning true to the very end of the function:
if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil {
// Do not allow multiple decimal separators
return false
}
if replacementTextHasLetter != nil {
// Do not allow letters
return false
}
return true
This reflects the all-or-nothing logic of the decision: all checks must succeed in order to all checks, while it takes only one failed check to reject the change.
let existingTextHasDecimalSeparator = textField.text?.range( of: ".")
let replacementTextHasDecimalSeparator = string.range( of: ".")
let charactersNotAllowed = NSCharacterSet.letters
let replacementTextHasLetter = string.rangeOfCharacter(from: charactersNotAllowed)
if existingTextHasDecimalSeparator != nil, replacementTextHasDecimalSeparator != nil{
if replacementTextHasLetter != nil{
return false
}
return false
} else {
if replacementTextHasLetter != nil{
return false
}
return true
}
Could improve like the one above, but it's how I got my app to not allow multiple decimals and characters. It works.

Resources