Monday, March 18, 2019

Xcode Error: iPhone has denied the launch request

i've been testing my project on my iPhone for many months without any problem. Today, it stopped working. When I click the Run button in Xcode to run the app on my device, Xcode showed the error message. I've tried many solutions on the net like deleting the provisioning profile through the Keychain Access app but no luck.


Solution


- Open Keychain Access app then select login in the upper left pane and Certificates in the lower left pane
- In the right pane, select the item "iPhone Developer: <my-email>" and press delete


After deleting the provisioning profile, Xcode will perform code signing and install a new provisioning profile into your device again when you run the project.

I don't know why we have to delete it but before I deleted the certificate in the Keychain Access app, I saw its expiration date had passed already. Perhaps, there was a bug in Xcode that it couldn't renew the certificate automatically.


Workaround


- From within Xcode (v9), select Product > Scheme > Edit Scheme...
- Select Run from the left pane then click on the Info tab and uncheck "Debug executable" checkbox

Someone here said that it's because i'm using Adhoc provisioning profile and this workaround just hide the issue. It doesn't fix it.



Reference
https://stackoverflow.com/questions/45421179/xcode-9-error-iphone-has-denied-the-launch-request


Thursday, March 14, 2019

Integrating Ad Banner in iOS App

I want to show an ad banner at the bottom of the screen in my app like below.

1. Create AdMob account


Go to https://apps.admob.com/signup/ and follow the instructions. Note that AdMob requires Google Adsense account.

AdMob provides SDK for developers to integrate an ad banner (GADBanerView) into their apps. It has many other features like showing only the most payable ads.

2. Download AdMob's SDK and add it to the project


- Download the zip file from here https://developers.google.com/admob/ios/download
- Extract it to a directory, for example: /Volumes/Data/Workspace/Frameworks
- Add it to the project in Xcode by following the instruction here

3. Initialize Mobile Ads SDK


Call configure(withApplicationID:) method in AppDelegate.swift as following:

import GoogleMobileAds

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        GADMobileAds.configure(withApplicationID: "ca-app-pub-3940256099942544~1458002511")
        return true
    }
}
 

The app ID is a test app ID created by AdMob for public use during development. Using the production app ID for development might cause the AdMob account suspended.

4. Update Info.plist file


Open Info.plist file and add a new key, GADApplicationIdentifier , with the string value of the AdMob (test) App ID under Information Property List. It's required by AdMob's SDK.


5. Add GADBannerView to the view hierarchy


In Interface Builder, drag a UIView from the Object Library and add it to the storyboard. Then, open the Identity Inspector and change the Class setting under Custom Class to GADBannerView. Here is the list of the standard banner sizes. I chose 320x50.


6. Configure GADBannerView's properties


Create an outlet named adBanner to connect the GADBannerView in the storyboard with the ViewController. Then, in the ViewController's viewDidLoad() method, initialize two required properties:
- rootViewController: the view controller used to present an overlay when the ad is clicked. Usually, it's set to the view controller that contains the GADBannerView.
- adUnitID: obtain it while registering AdMob account. It identifies a banner view to display in the app.

adBanner.rootViewController = self
adBanner.adUnitID = "ca-app-pub-3940256099942544/2934735716" // test ID

7. Load an ad


Once the banner is placed and its properties are set, it's time to load the ad. Call the load() method in the viewDidLoad():
adBanner.rootViewController = self
adBanner.adUnitID = "ca-app-pub-3940256099942544/2934735716"
adBanner.load(GADRequest())

8. Ad event (optional)


Through the use of GADBannerViewDelegate, we can listen for lifecycle events such as when the ad is closed or the user leaves the app.



FAQs


> Who pay for the ad click or view to the app developer?
- App developers get paid through Google Adsense
- App developers use Google AdMob (a mobile ad platform) to integrate ads from Google advertisers into their apps. AdMob provides iOS or Android SDKs to the developers to do so. AdMob has useful features like showing only the most payable Ads.

> Google Adsense
- Earn money by embedding an ad banner in a blog or web site.
- You don't need to specify the payment method until the your earning has reached the threshold ($100)

> Google AdMob
- Provide SDKs (many types of banners) for mobile apps


References


https://developers.google.com/admob/ios/quick-start
https://developers.google.com/admob/ios/banner


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