Elegant 'Bounded' Methodology in Swift

Elegant `bounded` methodology in Swift

I would generalize this extension to any Comparable, so that more types can benefit from it. Also, I would change the parameter to be a ClosedRange<Self> rather than two separate Self parameters, because that's the more common way of handling ranges in Swift. That'll come in especially handy when dealing with array indices.

extension Comparable {
func clamped(to r: ClosedRange<Self>) -> Self {
let min = r.lowerBound, max = r.upperBound
return self < min ? min : (max < self ? max : self)
}
}

// Usage examples:
10.clamped(to: 0...5) // => 5
"a".clamped(to: "x"..."z") // => "x"
-1.clamped(to: 0...1) // => 0

Creating a type bound to a certain range in Swift

The best I can think of is to wrap an Int in a struct with an initializer that enforces your condition:

struct NameMe {
let value: Int

init?(_ value: Int) {
guard 2...200 ~= value else { return nil }
self.value = value
}
}

Whereas using Int alone would require a run-time condition check at every place a value of such a kind was needed, this technique limits this to only the places where such values are created in the first place. Once created, you can pass around NameMe instances around, knowing that their value meets the preconditions.

Standard way to clamp a number between two values in Swift

Swift 4/5

Extension of Comparable/Strideable similar to ClosedRange.clamped(to:_) -> ClosedRange from standard Swift library.

extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}

#if swift(<5.1)
extension Strideable where Stride: SignedInteger {
func clamped(to limits: CountableClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#endif

Usage:

15.clamped(to: 0...10) // returns 10
3.0.clamped(to: 0.0...10.0) // returns 3.0
"a".clamped(to: "g"..."y") // returns "g"

// this also works (thanks to Strideable extension)
let range: CountableClosedRange<Int> = 0...10
15.clamped(to: range) // returns 10

Safe (bounds-checked) array lookup in Swift, through optional bindings?

Alex's answer has good advice and solution for the question, however, I've happened to stumble on a nicer way of implementing this functionality:

extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}


Example

let array = [1, 2, 3]

for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}

Create generic extension for Int, Double, CGFloat

Here's the extension I use. It uses nested conditionals instead of the max/min approach. This allows it to potentially short circuit one of the branches, which can improve performance (if it matters).

extension Comparable {
func clamped(to r: ClosedRange<Self>) -> Self {
let min = r.lowerBound, max = r.upperBound
return self < min ? min : (max < self ? max : self)
}
}

10.clamped(to: 0...5) // => 5

Figure out size of UILabel based on String in Swift

Use an extension on String

Swift 3

extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

return ceil(boundingBox.height)
}

func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

return ceil(boundingBox.width)
}
}

and also on NSAttributedString (which is very useful at times)

extension NSAttributedString {
func height(withConstrainedWidth width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

return ceil(boundingBox.height)
}

func width(withConstrainedHeight height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

return ceil(boundingBox.width)
}
}

Swift 4 & 5

Just change the value for attributes in the extension String methods

from

[NSFontAttributeName: font]

to

[.font : font]

How do you use String.substringWithRange? (or, how do Ranges work in Swift?)

You can use the substringWithRange method. It takes a start and end String.Index.

var str = "Hello, playground"
str.substringWithRange(Range<String.Index>(start: str.startIndex, end: str.endIndex)) //"Hello, playground"

To change the start and end index, use advancedBy(n).

var str = "Hello, playground"
str.substringWithRange(Range<String.Index>(start: str.startIndex.advancedBy(2), end: str.endIndex.advancedBy(-1))) //"llo, playgroun"

You can also still use the NSString method with NSRange, but you have to make sure you are using an NSString like this:

let myNSString = str as NSString
myNSString.substringWithRange(NSRange(location: 0, length: 3))

Note: as JanX2 mentioned, this second method is not safe with unicode strings.

How does String substring work in Swift

Sample Image

All of the following examples use

var str = "Hello, playground"

Swift 4

Strings got a pretty big overhaul in Swift 4. When you get some substring from a String now, you get a Substring type back rather than a String. Why is this? Strings are value types in Swift. That means if you use one String to make a new one, then it has to be copied over. This is good for stability (no one else is going to change it without your knowledge) but bad for efficiency.

A Substring, on the other hand, is a reference back to the original String from which it came. Here is an image from the documentation illustrating that.

No copying is needed so it is much more efficient to use. However, imagine you got a ten character Substring from a million character String. Because the Substring is referencing the String, the system would have to hold on to the entire String for as long as the Substring is around. Thus, whenever you are done manipulating your Substring, convert it to a String.

let myString = String(mySubstring)

This will copy just the substring over and the memory holding old String can be reclaimed. Substrings (as a type) are meant to be short lived.

Another big improvement in Swift 4 is that Strings are Collections (again). That means that whatever you can do to a Collection, you can do to a String (use subscripts, iterate over the characters, filter, etc).

The following examples show how to get a substring in Swift.

Getting substrings

You can get a substring from a string by using subscripts or a number of other methods (for example, prefix, suffix, split). You still need to use String.Index and not an Int index for the range, though. (See my other answer if you need help with that.)

Beginning of a string

You can use a subscript (note the Swift 4 one-sided range):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

or prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

or even easier:

let mySubstring = str.prefix(5) // Hello

End of a string

Using subscripts:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

or suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

or even easier:

let mySubstring = str.suffix(10) // playground

Note that when using the suffix(from: index) I had to count back from the end by using -10. That is not necessary when just using suffix(x), which just takes the last x characters of a String.

Range in a string

Again we simply use subscripts here.

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range] // play

Converting Substring to String

Don't forget, when you are ready to save your substring, you should convert it to a String so that the old string's memory can be cleaned up.

let myString = String(mySubstring)

Using an Int index extension?

I'm hesitant to use an Int based index extension after reading the article Strings in Swift 3 by Airspeed Velocity and Ole Begemann. Although in Swift 4, Strings are collections, the Swift team purposely hasn't used Int indexes. It is still String.Index. This has to do with Swift Characters being composed of varying numbers of Unicode codepoints. The actual index has to be uniquely calculated for every string.

I have to say, I hope the Swift team finds a way to abstract away String.Index in the future. But until then, I am choosing to use their API. It helps me to remember that String manipulations are not just simple Int index lookups.



Related Topics



Leave a reply



Submit