Thursday, March 14, 2019

Add Third-Part Framework to Xcode Project

1. Create a new directory for third-party frameworks, for example, /Volumes/Data/Workspace/Frameworks

2. Copy the *.framework file to the new directory

3. Add the new directory to the Xcode's "Framework Search Paths" setting


4. Add the framework to the project in Xcode by select the project in Project Navigator > General > Linked Frameworks and Libraries then click on the plus sign and browse to the framework.



Note that I used Xcode 9 to test it.


References
https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Tasks/IncludingFrameworks.html

Tuesday, March 12, 2019

Rounded Corner UIButton

I'm developing a pet project and I want to display the buttons with rounded corners as shown in the image below.


I subclassed the UIButton and override the layoutSubviews() method as following:
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        
        self.layer.cornerRadius = 0.1 * bounds.size.width
    }
 

Concepts


Layers

Each view in Swift has one root layer, which might contain one or more sub layers. Layers are used to draw and animate the view's contents.


Corner Radius

The images below explain what the corner radius is. The longer the corner radius, the bigger rounded corner.





References

https://developer.apple.com/documentation/quartzcore/calayer
https://www.hackingwithswift.com/example-code/calayer/what-is-calayer


Handling Swipe Gesture in Swift

I'm developing a pet project and i need to swipe the screen left or right to switch between subviews as shown in the image below.


1. Add the following lines to the ViewController's viewDidLoad() method


    let right = UISwipeGestureRecognizer()
    right.direction = .right
    right.addTarget(self, action: #selector(swipeGestureHandler(_:)))
    buttonsContainerView.addGestureRecognizer(right)
        
    let left = UISwipeGestureRecognizer()
    left.direction = .left
    left.addTarget(self, action: #selector(swipeGestureHandler(_:)))
    buttonsContainerView.addGestureRecognizer(left)
 

The swipe gesture recognizer was added for the container view of those buttons. The buttons will detect the finger movement and propagate the event to their parent view. The parent/container view will call the handler method, swipeGestureHandler(_:), for the swipe event.

2. Add the event handler method

    
    @objc public func swipeGestureHandler(_ gestureRecognizer:UISwipeGestureRecognizer) {
        if gestureRecognizer.state == .ended
            && gestureRecognizer.numberOfTouchesRequired == 1 {
            
            if gestureRecognizer.direction == .right {
                slideInAdvancedOperationsView()
            } else if gestureRecognizer.direction == .left {
                slideInBasicOperationsView()
            }
        }
    }
 

The handler method must be marked with @objc.


Monday, March 11, 2019

Slide-in Subview in Swift

I'm developing a pet project, and I want to make a slide-in subview as shown in the image below.


1. Adding the slide-in view and animate it


@IBAction func historyButtonTouched(_ sender: HistoryButton) {
        
        if historyPopupHidden() {
            buttonsStackView.addSubview(historyPopupBackground!) // add and bring to front
            historyPopupBackground?.leadingAnchor.constraint(equalTo: buttonsStackView.leadingAnchor).isActive = true
            historyPopupBackground?.trailingAnchor.constraint(equalTo: buttonsStackView.trailingAnchor).isActive = true
            historyPopupBackground?.topAnchor.constraint(equalTo: buttonsStackView.topAnchor).isActive = true
            historyPopupBackground?.bottomAnchor.constraint(equalTo: buttonsStackView.bottomAnchor).isActive = true
            historyPopupBackground?.translatesAutoresizingMaskIntoConstraints = false

            buttonsStackView.addSubview(historyPopup!) // add and bring to front
            historyPopup?.leadingAnchor.constraint(equalTo: buttonsStackView.leadingAnchor).isActive = true
            historyPopup?.topAnchor.constraint(equalTo: buttonsStackView.topAnchor).isActive = true
            historyPopup?.bottomAnchor.constraint(equalTo: buttonsStackView.bottomAnchor).isActive = true
            historyPopup?.widthAnchor.constraint(equalToConstant: 280).isActive = true
            historyPopup?.translatesAutoresizingMaskIntoConstraints = false
            
            let tx = (historyPopup?.frame.width)!
            historyPopup?.transform = CGAffineTransform(translationX: -1 * tx, y: 0)
            UIView.animate(withDuration: 0.3, animations: {
                self.historyPopup?.transform = (self.historyPopup?.transform.translatedBy(x: tx, y: 0))!
            })
        } else {
            hideHistoryPopup()
        }
}
 

The historyPopup view is loaded from a nib file in viewDidLoad() method. The historyPopupBackground is just a custom UIView with a transparent black background and is initialized in viewDidLoad() as well. Read here to understand what CGAffineTransform is for.

2. Removing the subview



private func hideHistoryPopup() {
        let tx = (historyPopup?.frame.width)!

        UIView.animate(withDuration: 0.3, animations: {
            self.historyPopup?.transform = (self.historyPopup?.transform.translatedBy(x: -1 * tx, y: 0))!
        }, completion: { (finished: Bool) in
            if finished {
                self.historyPopup?.removeFromSuperview()
                self.historyPopupBackground?.removeFromSuperview()
            }
        })
}
 




Sunday, March 10, 2019

Download entire website like Apple's Developer Documentation on MacOS

1). Download and install SiteSucker application (the app is working fine on MacOS Sierra)

2). Open the app and click on the Settings icon then select Path pane and Paths to Exclude

3). Paste the following URLs (contains regular expressions) in the listbox

https://developer.apple.com/documentation/[a-rt-z].*
https://developer.apple.com/documentation/s[a-vx-z].*
 
4). Check the "Use Regular Expressions" checkbox

The URL patterns above allows me to download https://developer.apple.com/documentation/swift and any necessary resource files (JS, CSS, images, etc.) under or outside the directory. It won't download the other URLs such as:
https://developer.apple.com/documentation/foundation
https://developer.apple.com/documentation/mapkit
https://developer.apple.com/documentation/uikit
https://developer.apple.com/documentation/gamekit 

Saturday, March 9, 2019

Copy, Cut, and Paste Operations using Swift

Copy a string to the pasteboard


1). Write data to the pasteboard programmatically
    
UIPasteboard.general.setValue("hello", forPasteboardType: "public.plain-text")
 
2). Open another app for example, Google Chrome browser, and touch the address bar twice to show the edit menu (copy, cut, and paste commands). Then, touch Paste.


Concepts


Pasteboard


A pasteboard is an area for storing data shared within an app or between apps. There are two types of pasteboard: public/system and private/app. We can access the system pasteboard through the type property general of UIPasteboard class. The system pasteboard is shared among apps. The data written to the pasteboard by the previous app get overridden by the data written by the current app. There may be one or two items stored on the pasteboard. An item might group one or more types of the data. We should store multiple types of data so that it's compatible with many apps.


UTI (Uniform Type Identifier)


UTI is a string to identify a class of entities (documents, pasteboard data, and bundles). UTI replaces the incompatible existing methods of tagging data. Currently, for example, a JPEG file is identified by any of the following methods:
1. A four character type code of JPEG
2. A filename extension of .jpg
3. A filename extension of .jpeg
4. A MIME type of image/jpeg
With UTI, the data is identified by the string public.jpeg. Then, we can use a utility function to translate from one to the other.

The key advantage of UTI over the other tagging methods is a conformance hierarchy. For example, the UTI public.html, which defines HTML text, conforms to the base text identifier, public.text. In this case, conformance lets applications which can open the general text files identify HTML files as ones i can open as well.





References
https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/UsingCopy,Cut,andPasteOperations/UsingCopy,Cut,andPasteOperations.html#//apple_ref/doc/uid/TP40009542-CH11-SW21
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html#//apple_ref/doc/uid/TP40001319-CH202-CHDHIJDE


Friday, March 8, 2019

Swift Error: nib must contain exactly one top level object which must be a UITableViewCell instance

I'm developing a pet project, which requires loading a UITableViewCell from a XIB file. At first, it worked fine but after making some changes, I got the error when the system trying to load the UITableViewCell from a nib file to construct the UITableView object.

Cause


It's because I accidentally dragged a Tap Gesture Recognizer from the Object Library and dropped it on the UITableViewCell in IB.


Solution


Click on the Tap Gesture Recognizer icon and press Delete key.