Tuesday, December 18, 2018

How to auto resize a button's image according to the button's size in Swift 3

I'm developing a pet project called MyCalculator using Swift 3.


When the screen is rotated from portrait to landscape, more buttons appears then each button become smaller to fit the screen and does its image.


Without any additional codes, the image does not get smaller if its size is smaller than the button's in both portrait and landscape modes. This does not look good. What i want is, for example, the space between the image's top and the button's top should always equal 30% of the button's height and the image should get smaller or bigger according to the space.

I created a custom UIButton as following:

       
import Foundation
import UIKit

public class CustomButton : UIButton {
    private let offset = CGFloat(2.0) // 2 point

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        self.imageView?.contentMode = .scaleAspectFit
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        
        let edge = bounds.height / 3 - offset
        self.imageEdgeInsets = UIEdgeInsets(top: edge, left: 0, bottom: edge, right: 0)
    }   
}
       

The following line means the button's image scales to fit the available space by maintaining the aspect ratio. In other words, if the image's height changes, its width changes too proportional to the height.


self.imageView?.contentMode = .scaleAspectFit 
       

The imageEdgeInsets property is used to set the spaces around the image on the button. A button can have both image and title (text) at the same time. The titleEdgeInsets property is used to define the spaces around the title. There is another related property called contentEdgeInsets, which is used to set the spaces around both image and title together. I only specified the values of the top and the bottom because I need the top and bottom spaces to be equal.

The layoutSubviews() method is called when bounds or frame property changes, according to https://forums.developer.apple.com/message/317152?et=watches.email.thread#317152.


Unreliable Solution


It works for my case but it's not guaranteed since the bounds property is a computed property, not a stored property so it might have its own instance variable, which might be set internally without going through the bounds property then didSet and willSet would not be called.

import Foundation
import UIKit

public class CustomButton : UIButton {
    private let offset = CGFloat(2.0) // 2 point
    
    public override var bounds: CGRect {
        didSet {
            self.boundDidChange()
        }
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        self.imageView?.contentMode = .scaleAspectFit
    }
    
    public func boundDidChange() {
        let edge = bounds.height / 3 - offset
        self.imageEdgeInsets = UIEdgeInsets(top: edge, left: 0, bottom: edge, right: 0)
    }
}
       



References
https://developer.apple.com/documentation/uikit/uiview/contentmode/scaleaspectfit
https://developer.apple.com/documentation/uikit/uibutton/1624034-imageedgeinsets

Friday, December 14, 2018

Displaying text and images together on UILabel in Swift 3

I'm developing a pet project called MyCalculator. I need to display the current operations the user is entering. I display the numbers and Math operators on a label. I inserted an image of  the square root of x operator in the label when the user touches the operator button.


The code snippet below show how to insert text and inline image in a UILabel.



    private var operatorImages: [Int:UIImage] = [
        103:UIImage(named: "squareRootX")!,
        116:UIImage(named: "cubeRootX")!,
        206:UIImage(named: "yRootX")!,
    ]
    private var operatorSymbolsCache : [Int:NSTextAttachment] = [:]
    
    private func getOperatorSymbol(_ tag: Int) -> NSTextAttachment {
        var result = operatorSymbolsCache[tag]
        if result == nil {
            result = NSTextAttachment()
            result?.image = operatorImages[tag]!
            result?.bounds = CGRect(x: 0.0, y: 0.0, width: Double(operatorImages[tag]!.size.width), height: Double(operatorImages[tag]!.size.height))
            operatorSymbolsCache[tag] = result
        }
        return result!
    }

    ...

    var myText = NSMutableAttributedString()
    myText.append(NSAttributedString(string: operand))
    myText.append(NSAttributedString(attachment: getOperatorSymbol(currentOperations[i].1.tag)))

    currentOperationsLabel.attributedText = myText
 
       
 

The problem is the operator image is not vertically center like in the image below.


I fixed it by updating the line:



result?.bounds = CGRect(x: 0.0, y: 0, width: Double(operatorImages[tag]!.size.width), height: Double(operatorImages[tag]!.size.height))
 
       
 

to:



result?.bounds = CGRect(x: 0.0, y: Double((displaySubSection.font.capHeight - operatorImages[tag]!.size.height) / 2), width: Double(operatorImages[tag]!.size.width), height: Double(operatorImages[tag]!.size.height))
 
       

I changed the y parameter. The graph below show what capHeight property is.


The properties capHeight, size.height, and size.width are in point. If the image dimension is 30x30 pixels, the size.height property would be 15 (points) and size.width property would be 15 (points) when the app is running on high resolution devices such as iPhone 5s and iPhone 6 because one point contains 2 pixels. To get the property's value in pixels, multiply it by the scale property of the UIImage class.



References
https://developer.apple.com/documentation/uikit/uiimage/1624105-size
https://stackoverflow.com/questions/26105803/center-nstextattachment-image-next-to-single-line-uilabel





Thursday, December 13, 2018

How to cut out an image in Photoshop

1. Open an image in Photoshop
2. Hold down the left mouse button on the Lasso Tool (L) icon from the Tools panel and select Polygonal Lasso Tool (L) from the popup.
3. Trace around the image you want to cut out. When you're done, the selection marquee shows up to around the image.


4. Press Ctrl + J (or command + J on Mac) to cut out the image to a new layer. Then, click on the Indicates Layer Visibility icon in front of Layer 0 to see the cut image on Layer 1.



5. Open the destination image. Both source and destination images might be opening as tabs so drag those tabs out to make them floated. After that, drag the Layer 1 from the source image to the destination image.



Reference
https://www.youtube.com/watch?v=on5MX_h8cdI
https://www.youtube.com/watch?v=n9fwiNyDHLI

Sunday, December 9, 2018

Regular Expression in Swift 3

To string in the example below matches the pattern:


let str = "5.14152834920+3.14120395872="
let pattern = "5.14[0-9]*\\+{1}3.14[0-9]*\\={1}"
Bool match = try match(source: str, pattern: pattern)
print("They matched \(match)") // True

func match(source: String, pattern: String) throws -> Bool {
        let regex = try NSRegularExpression(pattern: pattern, options: [])
        return regex.firstMatch(in: source, options: [], range: NSMakeRange(0, source.utf16.count)) != nil
}


Patterns


- [0-9]*  means zero or more digits
- \\+{1} means one plus sign. The double backslashes escape the plus sign.
- \\={1} means one equal character

Unicode


The firstMatch() method converts the given String object to NSString object but the String's count property does not always return the same value as the NSString's length property. The former relies on extended grapheme clusters, in which two or more unicode scalars combined are considered as one character.

  1. let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
  2. // regionalIndicatorForUS is 🇺🇸

The later relies on 16-bit code units, in which one unicode scalar is considered as one character. In the case above, there are two unicode scalars so it's two characters.

Then, to tell the firstMatch() method to iterate from start to end of the string, The utf16.count property must be used instead of the count property.


Reference
https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html

Saturday, December 1, 2018

Swift's Unit Test: Comparing two floating-point numbers (in scientific notation)

Comparing two floating-point numbers in Unit test (using Swift 3's XCTAssertEqual() method) might fail if their precisions are different. For example, if the expected result is 3.3 but the actual result (of 10/3) is 3.333333333333333, the test fails. The solution is we specify the accuracy argument of the XCTAssertEqual() method to tell the framework that a small difference between their precisions do not matter.

let result = 3.3
XCTAssertEqual(result, 10/3, accuracy: 0.04)

In the code snippet above, the framework considers those two numbers are equal because they are just 0.033333333333333 different, which is less than the accuracy parameter 0.04.

Scientific Notation


In Swift, the double literal can be expressed in scientific notation like 1.234e10, which equals 12_340_000_000. The test in the example below will fail.

XCTAssertEqual(1.1234e10, 1.123e10)

To make the test successful, the accuracy parameter should be specified as following.

XCTAssertEqual(1.1234e10, 1.123e10, accuracy: 0.0004e10)
// 1.1234e10 = 11234_000_000
// 1.123e10  = 11230_000_000
// 0.0004e10 =     4_000_000

The accuracy value less than that such as 0.0003e10 or 0.0001e10 still make the test fail.