Government and its Test Pages

Philippine government sites are filled with “Test Pages”. Not sure if this is just pure incompetence. Or a honeypot. Or lack of budget.

https://dict.gov.ph/test-page-2/

Yes, this department should know better considering IT is its main function.

https://www.gov.ph/en/test-page?p_p_id=com_liferay_login_web_portlet_LoginPortlet&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&saveLastPath=false&_com_liferay_login_web_portlet_LoginPortlet_mvcRenderCommandName=%2Flogin%2Flogin

How about test page and sign in page combined. Nice.

https://www.gov.ph/test-page

How about a broken CSS currently live.

https://bfp.gov.ph/test-page/#.YCuH_y0RpB0

Another one. (DJ Khaled voice)

Even valid looking pages are still /test-page/

Create a camera shutter in Swift

I’m going to show how I achieved the camera shutter button and animation using Swift, more or less the same with the stock iOS camera app. Grab a coffee because this one’s going to be a little longer.

Storyboard UI

On your ViewController, drag a UIButton and place it at the bottom part. On the Size Inspector, give it a width and height of 65, thus creating a perfect square. Add the width and height constraint, also the bottom anchor.

And, also the horizontal alignment constraint to 0.

Before we proceed, there is a flaw in our UI implementation. As you noticed we shouldn’t use the bottom constraint because once we implement the scale or translate animation it will hold off and pull down the UIButton back to its proper place regardless of its size. What we would like to do is for UIButton to maintain its screen position regardless of its size.

So let’s add a container UIView that will house our UIButton. Drag another UIView and this time make sure it fills up the majority of the bottom area. In the Size Inspector the values should be: x=4, y=741, width=406, height=100. After, add the necessary constraints as you see fit. Place the UIButton inside this newly added container UIView. It should look like the one below.

Update the UIButton constraints with both vertically and horizontally constraints. And now we don’t have to worry our UIButton not scaling properly when resizing.

Now, for the border that will surround our UIButton. Drag a UIView inside the container UIView and behind the UIButton and make sure it is much more larger in terms of width and height. Set both width and height to 80. For our border UIView, add the following width and height constraints and also the horizontally and vertically constraint, same with our UIButton. It should look like the one below.

Coding Time

Now let’s code. Let’s wire the outlets to our ViewController. Also, include the width and height constraint of our UIButton.

class CustomCameraViewController: UIViewController {

    @IBOutlet weak var captureButton: UIButton!
    @IBOutlet weak var captureButtonWidthConstraint: NSLayoutConstraint!
    @IBOutlet weak var captureButtonHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var captureBorderView: UIView!
    private let videoCaptureOutput = AVCaptureMovieFileOutput()
}

In our viewDidLoad, let’s turn our squares (both capture UIButton and border UIView) into circles to signify a ready to record status.

override func viewDidLoad() {
        super.viewDidLoad()
        captureButton.layer.cornerRadius = captureButton.frame.size.width / 2
        captureButton.clipsToBounds = true
        captureBorderView.layer.borderWidth = 6.0
        captureBorderView.layer.borderColor = UIColor.red.cgColor
        captureBorderView.layer.cornerRadius = captureBorderView.frame.size.width / 2
}

Next, wire the IBAction from our capture UIButton. And below is all the code and as you can see I added a pulse animation just to make it more clearer the recording status.

@IBAction func captureTapped(_ sender: Any) {
    toggleVideoRecording()
}

private func toggleVideoRecording1() {
        if !videoCaptureOutput.isRecording {
            DispatchQueue.main.async {
                UIView.animate(withDuration: 0.2, animations: {
                    self.captureButtonWidthConstraint.constant = 35
                    self.captureButtonHeightConstraint.constant = 35
                    self.captureButton.layer.cornerRadius = 9
                    UIView.animate(withDuration: 0.4, animations: {
                        let pulseAnimation = CABasicAnimation(keyPath: "opacity")
                        pulseAnimation.duration = 0.5
                        pulseAnimation.fromValue = 0
                        pulseAnimation.toValue = 1
                        pulseAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
                        pulseAnimation.autoreverses = true
                        pulseAnimation.repeatCount = .infinity
                        self.captureBorderView.layer.add(pulseAnimation, forKey: "Pulse")
                    })
                    self.captureButton.layoutIfNeeded()
                })
            }
        } else {
            // Stop recording
            videoCaptureOutput.stopRecording()
            DispatchQueue.main.async {
                UIView.animate(withDuration: 0.2, animations: {
                    self.captureButtonWidthConstraint.constant = 65
                    self.captureButtonHeightConstraint.constant = 65
                    self.captureButton.layer.cornerRadius = 32.5
                    UIView.animate(withDuration: 0.4, animations: {
                        self.captureBorderView.layoutIfNeeded()
                        self.captureBorderView.layer.removeAnimation(forKey: "Pulse")
                    })
                    self.captureButton.layoutIfNeeded()
                })
            }
        }
    }

Let me know if you have any questions and clarifications. You can email me at lawgimenez@hey.com directly.

Create a custom camera using Swift

If you are looking to create your own camera on iOS, I will demonstrate how I did it on my end.

CameraPreviewView

First of all you need to subclass UIView and inherit its properties. We will use this Swift file for our camera preview.

import UIKit
import AVFoundation

class CameraPreviewView: UIView {
    
    override class var layerClass: AnyClass {
        return AVCaptureVideoPreviewLayer.self
    }
    
    var videoPreviewLayer: AVCaptureVideoPreviewLayer {
        return layer as! AVCaptureVideoPreviewLayer
    }
    
    var session: AVCaptureSession? {
        get {
            return videoPreviewLayer.session
        }
        set {
            videoPreviewLayer.session = newValue
        }
    }
}

AVCaptureVideoPreviewLayer is a subclass of CALayer that you use to display video as it’s captured by an input device.

Storyboard UI

Drag a UIView that should fill up the whole screen. Add the necessary constraints at each border. Then place a UIButton at the lower part and also add the necessary constraints. This UIButton should serve as the record button.

Take note that the UIButton should be on top of the camera preview UIView as not to hide it once the preview is shown.

Click the UIView and add CameraPreviewView as the custom class.

CaptureViewController

In our ViewController above, create a file called CaptureViewController.swift and use it as the custom class. Then, wire the camera preview UIView and UIButton outlets. Then import AVFoundation and add the necessary classes like the code below.

import UIKit
import AVFoundation
import OSLog

class CaptureViewController: UIViewController {

    @IBOutlet weak var cameraPreviewView: CameraPreviewView!
    @IBOutlet weak var captureButton: UIButton!
    // An object that manages capture activity and coordinates the flow of data from input devices to capture outputs
    private let captureSession = AVCaptureSession()
    // A capture output that records video and audio to a QuickTime movie file.
    private let videoCaptureOutput = AVCaptureMovieFileOutput()
    // A capture input that provides media from a capture device to a capture session.
    private var activeCaptureDeviceInput: AVCaptureDeviceInput!
    private let logger = Logger()
    private let sessionQueue = DispatchQueue(label: "Capture Session")

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        initCaptureSession()
    }

    private func initCaptureSession() {
        cameraPreviewView.session = captureSession
        // First of all ask permission from the user
        if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
            // Start capturing video
            startVideoSession()
        } else if AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined {
            // Request permission
            AVCaptureDevice.requestAccess(for: .video, completionHandler: { [self]
                granted in
                if granted {
                    logger.debug("Video capture device granted? \(granted)")
                    startVideoSession()
                }
            })
        }
    }

    private func startVideoSession() {
        captureSession.beginConfiguration()
        captureSession.sessionPreset = .hd4K3840x2160
        // Setup camera
        let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
        guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!) else {
            return
        }
        if captureSession.canAddInput(videoDeviceInput) {
            captureSession.addInput(videoDeviceInput)
            activeCaptureDeviceInput = videoDeviceInput
        }
        // Setup microphone
        let audioDevice = AVCaptureDevice.default(for: .audio)
        guard let audioDeviceInput = try? AVCaptureDeviceInput(device: audioDevice!) else {
            return
        }
        if captureSession.canAddInput(audioDeviceInput) {
            captureSession.addInput(audioDeviceInput)
        }
        // Setup movie output
        if captureSession.canAddOutput(videoCaptureOutput) {
            captureSession.addOutput(videoCaptureOutput)
        }
        DispatchQueue.main.async {
            self.cameraPreviewView.videoPreviewLayer.connection?.videoOrientation = .portrait
        }
        captureSession.commitConfiguration()
        captureSession.startRunning()
    }

}

That’s it. Try running it on a connected device and not on the emulator.

2020 Was a Mixed Year

Mixed year because as good as the metrics are compared to last year, it saddens me that a huge number of people are laid off and companies closed because of Covid-19. Hopefully 2021 will be different. Here’s a sneak peek of the OnlineJobs.ph app metrics during lockdown.

In terms of number of installations, active devices and overall metrics, OnlineJobs.ph experienced significant gains throughout the year 2020. I rewrote the app, from React Native to Swift, back in January 2020 and released the new version a month after. Same with Android, I rewrote it from React Native to Kotlin and released it a month after iOS release date.

Also in terms of UX design I also redesign the app to make it more in line with the platform’s guidelines. Below are my initial sketches. My app inspirations for this redesign were VSCO (very evident on the menu) and Apollo.

iOS

As you can see on the graphs, May – July 2020 was peak lockdown because of Covid-19. I also assumed many were laid off and installed OnlineJobs.ph app to look for jobs and new opportunities.

Same metrics with Sessions, May – July 2020 had a significant bump on installations during peak lockdown.

Impressions metrics also saw a significant bump. These are the number of times the app showed up on the App Store.

Android

OnlineJobs.ph for Android also saw a huge bump on metrics and still continues to rise.

Android app also, during May – July 2020, saw an increase in new users during lockdown.

2021

For 2021, new versions are coming for iOS. Significant fixes for Android too.

I am aware that the OnlineJobs.ph for Android needs major improvements and fixes, I received a large number of issues and crashes and I am looking forward to resolve it for 2021. Apologies if you are affected and know that I am hard at work on resolving them. Thank you for using the app. If you have any questions, issues or crashes you can email me directly at lawgimenez@hey.com.