1. A complete development process

In general, a normal process includes:

  • Product requirements, give prototype diagram
  • Team confirmation of requirements
  • Start from the designer to design drawings, synchronous development to do the preparatory work before development, such as technical investigation, how to cooperate with the front and back end
  • After the designer has completed the design, the team will compare the design to the prototype to confirm the requirements. After the team confirms the requirements, the designer will post the design to Blue Lake or Zeplin
  • The development team begins development work
  • After development, developers test themselves. Generally, the unit test code we write during development is also self-tested
  • Developers test themselves and the product tests. In fact, during the development process, the product should also focus on the development in real time
  • Let a professional tester do the test
  • Release a private beta and do large-scale testing
  • Submit to App Store for review

2. Storyboard startup process

Normally, if you create an Objective-C project, by default you call main first, and inside main you call UIApplicationMain.

int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

But in the Swift project, there is no main.m file or main function. The Swift project uses an @UIApplicationMain tag in the AppDelegate. So what does UIApplicationMain do:

  • createUIApplicationsingleton
  • createUIApplicationDelegate object of, namelyAppDelegate
  • Start the event loop to listen for system events and notify when the corresponding system event is strongAppDelegate
  • Create the lowest levelUIWindowsObject,
  • readInfo.plistIs set to start by defaultstoryboardThe file name
  • Load setupis Initial View ControllerstoryboardFile, while creating the correspondingView Controllerobject
  • Set createdView ControllerforUIWindowsThe root of the viewrootViewController
  • According toWindowsIn trying to

Run the project and you’ll see the following: At the bottom is a UIWindwos

3. No Storyboard to start App

Storyboard starts with the Initial View Controller set in main.storyboard by default. How do we set our Initial view Controller?

RPChat_iOS is the UI display and interaction related code, so in RPChat_iOS create a new folder SignIn

The new login interface Controller is named SignInViewController

In AppDelegatedidFinishLaunchingWithOptions launchOptions callback methods add code, set to start SignInViewController as the default controller:

if #available(iOS 13, *) { } else { window = UIWindow.init() window? .frame = UIScreen.main.bounds window?.makeKeyAndVisible() let signInVC = SignInViewController() window?.rootViewController = signInVC }Copy the code

Add code to the SceneDelegateoptions connectionOptions method:

guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(frame: windowScene.coordinateSpace.bounds) window? .windowScene = windowScene window? .backgroundColor = .white let tabBar = SignInViewController() window? .rootViewController = tabBar window? .makeKeyAndVisible()Copy the code

4. Implementation of login UI

Take a look at the final image:

  • 1.Auto Layout

As for UI, CONSIDERING version compatibility and later maintenance, I adopted the system NSLayoutAnchor adaptation.

NSLayoutAnchor Common property

  • leadingAnchor
  • trailingAnchor
  • leftAnchor
  • rightAnchor
  • topAnchor
  • bottomAnchor
  • widthAnchor
  • heightAnchor
  • centerXAnchor
  • centerYAnchor
  • firstBaselineAnchor
  • lastBaselineAnchor

For more details about using Auto Layout, please refer to the official document:

High Performance Auto Layout

Apple Develope NSLayoutConstraint

Apple Develope NSLayoutAnchor

WWDC 2018 What’s New in Cocoa Touch

  • 2. UI implementation

Create a View named SignInRootView as the main View of the login interface, and initialize the View in lazy loading mode

The top of the Logo image implementation code:

lazy var logoImg: UIImageView = {
        self.addSubview($0)
        $0.translatesAutoresizingMaskIntoConstraints = false
        let top = $0.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 44)
        let centerX = $0.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0)
        let width = $0.widthAnchor.constraint(equalToConstant: 120)
        let height = $0.heightAnchor.constraint(equalToConstant: 120)
        NSLayoutConstraint.activate([top, centerX, width, height])
        return $0
    }(UIImageView())
Copy the code

User name input box implementation code:

 lazy var accountNumberView: UIView = {
        self.addSubview($0)
        $0.translatesAutoresizingMaskIntoConstraints = false
        let top = $0.topAnchor.constraint(equalTo: logoImg.bottomAnchor, constant: 20)
        let left = $0.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 40)
        let right = $0.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -40)
        let height = $0.heightAnchor.constraint(equalToConstant: 50)
        NSLayoutConstraint.activate([top, left, right, height])
        $0.layer.cornerRadius = 25
        return $0
    }(UIView())
 
    
    lazy var accountNumberLab: UITextField = {
        accountNumberView.addSubview($0)
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.topAnchor.constraint(equalTo: accountNumberView.topAnchor, constant: 0).isActive = true
        $0.leftAnchor.constraint(equalTo: accountNumberView.leftAnchor, constant: 16).isActive = true
        $0.rightAnchor.constraint(equalTo: accountNumberView.rightAnchor, constant: -16).isActive = true
        $0.bottomAnchor.constraint(equalTo: accountNumberView.bottomAnchor, constant: 0).isActive = true
        $0.font = UIFont.init(name: "PingFangTC-Semibold", size: 19)
        return $0
    }(UITextField())
Copy the code

Password input box implementation code:

lazy var inputPasswordView: UIView = {
           self.addSubview($0)
           $0.translatesAutoresizingMaskIntoConstraints = false
           let top = $0.topAnchor.constraint(equalTo: accountNumberView.bottomAnchor, constant: 20)
           let left = $0.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 40)
           let right = $0.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -40)
           let height = $0.heightAnchor.constraint(equalToConstant: 50)
           NSLayoutConstraint.activate([top, left, right, height])
           $0.layer.cornerRadius = 25
           return $0
       }(UIView())
    
       
    lazy var inputPasswordTxt: UITextField = {
           inputPasswordView.addSubview($0)
           $0.translatesAutoresizingMaskIntoConstraints = false
           $0.topAnchor.constraint(equalTo: inputPasswordView.topAnchor, constant: 0).isActive = true
           $0.leftAnchor.constraint(equalTo: inputPasswordView.leftAnchor, constant: 16).isActive = true
           $0.rightAnchor.constraint(equalTo: inputPasswordView.rightAnchor, constant: -16).isActive = true
           $0.bottomAnchor.constraint(equalTo: inputPasswordView.bottomAnchor, constant: 0).isActive = true
           $0.font = UIFont.init(name: "PingFangTC-Semibold", size: 19)
           return $0
       }(UITextField())
Copy the code

Login button implementation code:

lazy var signInBtn: UIButton = { self.addSubview($0) $0.translatesAutoresizingMaskIntoConstraints = false let top = $0.topAnchor.constraint(equalTo: inputPasswordView.bottomAnchor, constant: 20) let left = $0.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 40) let right = $0.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -40) let height = $0.heightAnchor.constraint(equalToConstant: 50) NSLayoutConstraint.activate([top, left, right, height]) $0.layer.cornerRadius = 25 $0.titleLabel? .font = UIFont.init(name: "PingFangTC-Semibold", size: 20) $0.setTitle(NSLocalizedString("Sign In", comment: ""), for: .normal) return $0 }(UIButton())Copy the code

Here is more code, see the specific implementation code: gitub RPChat

  • 3. Set the background color

Since the color given by the designer is usually hexadecimal, a transcoding process is needed here:

public class func hexStringToColor(_ hexadecimal: String) -> UIColor { var cstr = hexadecimal.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() as NSString; if(cstr.length < 6){ return UIColor.clear; } if(cstr.hasPrefix("0X")){ cstr = cstr.substring(from: 2) as NSString } if(cstr.hasPrefix("#")){ cstr = cstr.substring(from: 1) as NSString } if(cstr.length ! = 6){ return UIColor.clear; } var range = NSRange.init() range.location = 0 range.length = 2 let rStr = cstr.substring(with: range); range.location = 2; let gStr = cstr.substring(with: range) range.location = 4; let bStr = cstr.substring(with: range) var r :UInt32 = 0x0; var g :UInt32 = 0x0; var b :UInt32 = 0x0; Scanner.init(string: rStr).scanHexInt32(&r); Scanner.init(string: gStr).scanHexInt32(&g); Scanner.init(string: bStr).scanHexInt32(&b); Return uicolor.init (red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, Blue: CGFloat(b)/255.0, alpha: 1)} uicolor.init (red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: 1)}Copy the code

Then you can set the background color using the hexadecimal color given by the designer:

signInBtn.backgroundColor = UIColor.hexStringToColor("0xF5BE62")
Copy the code
  • 4, Drak Mode adaptation

(1) Text and UIView background color adaptation

Since Apple handles Drak Mode after iOS 13, developers should also do the corresponding compatibility processing. Now I do not make compatibility processing in the code, at this time adjust the emulator to dark mode, running the project can see that in dark mode, the background color of the user name and password input box is missing. At this point, dark mode compatibility processing should be done.

Here, I did the extension processing for UIColor, and then I did the corresponding processing for Drak Mode and Light Mode respectively in the extension method. The code is as follows:

Extension UIColor {/// extension UIColor public class var drakMode: Bool {if #available(iOS 13.0, *) { let currentMode = UITraitCollection.current.userInterfaceStyle if currentMode == .dark { return true } } return False} public class func isDrakMode() -> Bool {if #available(iOS 13.0, *) { let currentMode = UITraitCollection.current.userInterfaceStyle if currentMode == .dark { return true } } return } /// UIView public class var darkModeViewColor: UIColor {if #available(iOS 13.0, *) { return .systemBackground } else { return .white } } public class func configDarkModeViewColor() -> UIColor { if # available (iOS 13.0, Return. SystemBackground} else {return. White}} public class var darkModeTextColor: UIColor {if #available(iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return .white } else { return .black } } } else { return .black } } public class func configDarkModeTxtColor() -> UIColor { if # available (iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return .white } Else {return. Black}} else {return. Black}} /// subuiView background color public class var subDarkModeViewColor: UIColor {if #available(iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return UIColor(red:  100/255, green: 100/255, blue: 100/255, alpha: 1) } else { return .groupTableViewBackground } } } else { return .groupTableViewBackground } } public class func ConfigSubDarkModeViewColor () - > UIColor {# if available (iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return UIColor(red:  100/255, green: 100/255, blue: 100/255, alpha: 1)} else {return. GroupTableViewBackground}}} else {return. GroupTableViewBackground}} / / / set the default public view with color background  class func configDarkModeViewWith(_ dfaultColor: UIColor) -> UIColor {if #available(iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return UIColor(red:  100/255, green: 100/255, blue: 100/255, alpha: 1)} else {return dfaultColor}} else {return dfaultColor}} public class func configDarkModeTxtColorWith(_ dfaultColor: UIColor) -> UIColor {if #available(iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return .systemBackground} else {return dfaultColor}} else {return dfaultColor}} public class var  placeholderColor: UIColor {if #available(iOS 13.0, *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return UIColor(red: 255, green: 255, blue: 255, alpha: 0.25)} else {return UIColor(red: 0, green: 0, blue: 0, alpha: Else {return UIColor(red: 0, green: 0, blue: 0, alpha: 0)}} else {return UIColor(red: 0, green: 0, blue: 0, alpha: 0) }} public class tagColor () -> UIColor {if #available(iOS 808)}} public class tagcolor () -> UIColor {if #available(iOS 808) *) { return UIColor{(trainCollection) -> UIColor in if trainCollection.userInterfaceStyle == .dark { return UIColor(red: 255, green: 255, blue: 255, alpha: 0.25)} else {return UIColor(red: 0, green: 0, blue: 0, alpha: Else {return UIColor(red: 0, green: 0, blue: 0, alpha: 0.25)}}Copy the code

Just set the color using a method or property:

accountNumberView.backgroundColor = .subDarkModeViewColor
inputPasswordView.backgroundColor = .subDarkModeViewColor
Copy the code
accountNumberView.backgroundColor = UIColor.configSubDarkModeViewColor()
inputPasswordView.backgroundColor = UIColor.configSubDarkModeViewColor()
Copy the code

In terms of results, there is no difference between the two approaches. It’s usually a little bit more natural to set or evaluate with a property, for example if I use a = 0 it’s a little bit more natural than if I use a = getData(). The book Clearn Code emphasizes the use of verbs or phrasal verbs when naming methods or functions, so the usual Code for using a method is to do something. I’m just changing the background color of UIView here, so I personally think it’s more intuitive to use properties.

(2) Picture DrakMode adaptation

Change the image to Any,Dark in Xcode, and Xcode will automatically generate a new set of fill images in Dark mode. Just drag the image in.

(3) Monitor the changes of traitCollection

In some special places, we need to deal with them according to whether the current system is in dark mode. For example, under general circumstances, the privacy agreement is loaded with WKWebView. Under normal circumstances, it is white background with black text, and under dark mode, it is black background with white text. In this case, you need to listen to whether the current system is in dark mode.

What I do is to set up a DrakModeProtocol, and in the Controller that I need to listen to, I just follow this protocol.

Public Protocol DrakModeProtocol: NSObjectProtocol {@available(iOS 13.0, *) var traitCollection: UITraitCollection {get} @available(iOS 13.0, *) func traitCollectionDidChange(_ traitCollection: UITraitCollection?) }Copy the code
// Use extension MineWKWebViewController: DrakModeProtocol { override func traitCollectionDidChange(_ traitCollection: UITraitCollection?) {# if available (iOS 13.0, *) { if UITraitCollection.current.userInterfaceStyle == .dark { webView.evaluateJavaScript("document.body.style.backgroundColor=\"#000000\"") { (data, error) in } webView.evaluateJavaScript("document.body.style.webkitTextFillColor=\"#FFFFFF\"") { (data, error) in } } else { webView.evaluateJavaScript("document.body.style.backgroundColor=\"#FFFFFF\"") { (data, error) in } webView.evaluateJavaScript("document.body.style.webkitTextFillColor=\"#000000\"") { (data, error) in } } } } }Copy the code

Run the project again and you can see that the interface is compatible with dark mode:

This paper mainly writes:

  • Launch App without storyboard
  • Implementation of login UI
  • Drak Mode adaption

Demo: Github RPChat