Use MVVM style

Finish login part
This commit is contained in:
secminhr 2018-07-06 23:01:43 +08:00
parent a989cc4c34
commit 65582f0842
8 changed files with 236 additions and 35 deletions

View File

@ -8,20 +8,22 @@
/* Begin PBXBuildFile section */
4F2F116220EDB07B0074B9A6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2F116120EDB07B0074B9A6 /* AppDelegate.swift */; };
4F2F116420EDB07B0074B9A6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2F116320EDB07B0074B9A6 /* ViewController.swift */; };
4F2F116420EDB07B0074B9A6 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2F116320EDB07B0074B9A6 /* LoginViewController.swift */; };
4F2F116720EDB07B0074B9A6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F2F116520EDB07B0074B9A6 /* Main.storyboard */; };
4F2F116920EDB07E0074B9A6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F2F116820EDB07E0074B9A6 /* Assets.xcassets */; };
4F2F116C20EDB07E0074B9A6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F2F116A20EDB07E0074B9A6 /* LaunchScreen.storyboard */; };
4F2F117420EF0E5B0074B9A6 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2F117320EF0E5B0074B9A6 /* LoginViewModel.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
4F2F115E20EDB07B0074B9A6 /* StudyCK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StudyCK.app; sourceTree = BUILT_PRODUCTS_DIR; };
4F2F116120EDB07B0074B9A6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
4F2F116320EDB07B0074B9A6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
4F2F116320EDB07B0074B9A6 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
4F2F116620EDB07B0074B9A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
4F2F116820EDB07E0074B9A6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
4F2F116B20EDB07E0074B9A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
4F2F116D20EDB07E0074B9A6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4F2F117320EF0E5B0074B9A6 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -55,7 +57,8 @@
isa = PBXGroup;
children = (
4F2F116120EDB07B0074B9A6 /* AppDelegate.swift */,
4F2F116320EDB07B0074B9A6 /* ViewController.swift */,
4F2F116320EDB07B0074B9A6 /* LoginViewController.swift */,
4F2F117320EF0E5B0074B9A6 /* LoginViewModel.swift */,
4F2F116520EDB07B0074B9A6 /* Main.storyboard */,
4F2F116820EDB07E0074B9A6 /* Assets.xcassets */,
4F2F116A20EDB07E0074B9A6 /* LaunchScreen.storyboard */,
@ -135,8 +138,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4F2F116420EDB07B0074B9A6 /* ViewController.swift in Sources */,
4F2F116420EDB07B0074B9A6 /* LoginViewController.swift in Sources */,
4F2F116220EDB07B0074B9A6 /* AppDelegate.swift in Sources */,
4F2F117420EF0E5B0074B9A6 /* LoginViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
version = "2.0">
</Bucket>

View File

@ -1,24 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="rri-3c-Ev2">
<device id="retina5_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<!--Navigation Controller-->
<scene sceneID="iks-VI-BDd">
<objects>
<navigationController id="rri-3c-Ev2" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translucent="NO" id="xZn-kE-rxp">
<rect key="frame" x="0.0" y="20" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" red="0.80784313725490198" green="0.77254901960784317" blue="0.86274509803921573" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.80784313725490198" green="0.77254901960784317" blue="0.86274509803921573" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBar>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="B3Z-yC-lfb"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="wXr-SM-2lL" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1105" y="-12"/>
</scene>
<!--夢駝林-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" customClass="LoginViewController" customModule="StudyCK" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="672"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="帳號" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="H2y-Q3-tQ8">
<rect key="frame" x="77.666666666666686" y="237" width="259" height="30"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="259" id="AI2-08-G3u"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" returnKeyType="next" enablesReturnKeyAutomatically="YES" textContentType="username"/>
<connections>
<action selector="loginFieldChanged:" destination="BYZ-38-t0r" eventType="editingChanged" id="gty-uy-om3"/>
</connections>
</textField>
<textField opaque="NO" tag="1" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="密碼" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="ycA-mV-AIB">
<rect key="frame" x="77.666666666666686" y="267" width="259" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="259" id="iC3-zF-5UN"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" returnKeyType="go" enablesReturnKeyAutomatically="YES" secureTextEntry="YES" textContentType="password"/>
<connections>
<action selector="loginFieldChanged:" destination="BYZ-38-t0r" eventType="editingChanged" id="tq6-Sn-dby"/>
</connections>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="請先登入" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vsn-x8-d9a">
<rect key="frame" x="172" y="143" width="70" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gsp-Sh-Qg6">
<rect key="frame" x="77.666666666666686" y="307" width="259" height="30"/>
<color key="backgroundColor" red="0.49437394281736569" green="0.47342670436823042" blue="0.52517083768786699" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="259" id="G3f-KU-PO4"/>
</constraints>
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="15"/>
<state key="normal" title="登入">
<color key="titleColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="loginButtonClicked" destination="BYZ-38-t0r" eventType="touchUpInside" id="LEo-KG-oIl"/>
</connections>
</button>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="yGw-0D-gkU">
<rect key="frame" x="233.66666666666666" y="312" width="20" height="20"/>
</activityIndicatorView>
</subviews>
<color key="backgroundColor" red="0.80784313725490198" green="0.77254901960784317" blue="0.85882352941176465" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="gsp-Sh-Qg6" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="4Z6-Fp-jZS"/>
<constraint firstItem="Vsn-x8-d9a" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="143" id="ELK-wz-bun"/>
<constraint firstItem="ycA-mV-AIB" firstAttribute="top" secondItem="H2y-Q3-tQ8" secondAttribute="bottom" id="Ee6-Ta-j4e"/>
<constraint firstItem="H2y-Q3-tQ8" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="IvQ-qe-CWj"/>
<constraint firstItem="ycA-mV-AIB" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="Lsk-5u-Oil"/>
<constraint firstItem="yGw-0D-gkU" firstAttribute="centerY" secondItem="gsp-Sh-Qg6" secondAttribute="centerY" id="M2L-J8-hDh"/>
<constraint firstItem="gsp-Sh-Qg6" firstAttribute="top" secondItem="ycA-mV-AIB" secondAttribute="bottom" constant="10" id="fGg-NJ-h7T"/>
<constraint firstItem="Vsn-x8-d9a" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="sbc-Eu-vXL"/>
<constraint firstItem="yGw-0D-gkU" firstAttribute="leading" secondItem="gsp-Sh-Qg6" secondAttribute="trailing" constant="-103" id="spG-Ph-lfm"/>
<constraint firstItem="H2y-Q3-tQ8" firstAttribute="top" secondItem="Vsn-x8-d9a" secondAttribute="bottom" constant="73" id="xeu-BM-Cge"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<navigationItem key="navigationItem" title="夢駝林" id="FiP-ub-A86"/>
<connections>
<outlet property="loginIndicator" destination="yGw-0D-gkU" id="DIp-iA-grp"/>
<segue destination="2Lr-EI-56s" kind="show" id="Ym1-E9-RDg"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-362.39999999999998" y="-12.561576354679804"/>
</scene>
<!--Table View Controller-->
<scene sceneID="O1P-xZ-ktC">
<objects>
<tableViewController id="2Lr-EI-56s" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="DAj-3H-Ix0">
<rect key="frame" x="0.0" y="0.0" width="414" height="672"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="vxr-wL-rAf">
<rect key="frame" x="0.0" y="28" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="vxr-wL-rAf" id="TFk-cM-I0Q">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="2Lr-EI-56s" id="SQM-kS-chI"/>
<outlet property="delegate" destination="2Lr-EI-56s" id="8Lj-ab-PiK"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="4bz-Gh-OxZ" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="444" y="-11"/>
</scene>
</scenes>
</document>

View File

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>

View File

@ -0,0 +1,50 @@
import UIKit
class LoginViewController: UIViewController {
let viewModel = LoginViewModel()
@IBOutlet weak var loginIndicator: UIActivityIndicatorView!
@IBAction func loginFieldChanged(_ sender: UITextField) {
if sender.tag == 0 {
viewModel.username = sender.text!
} else {
viewModel.password = sender.text!
}
}
@IBAction func loginButtonClicked() {
viewModel.login()
}
override func viewDidLoad() {
viewModel.showLoginFailedAlert = { (title:String, message:String) -> Void in
DispatchQueue.main.async {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "重試", style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
viewModel.loginStateChanged = { (loggingIn: Bool) -> Void in
DispatchQueue.main.async {
self.loginIndicator.setAnimating(to: loggingIn)
}
}
viewModel.loginSuccessfully = {
DispatchQueue.main.async {
}
}
}
}
extension UIActivityIndicatorView {
func setAnimating(to animating: Bool) {
if animating {
startAnimating()
} else {
stopAnimating()
}
}
}

View File

@ -0,0 +1,44 @@
import Foundation
class LoginViewModel {
var username: String = ""
var password: String = ""
var loginState = false {
didSet {
self.loginStateChanged?(loginState)
}
}
let loginURL = "http://study.ck.tp.edu.tw/login_chk.asp"
let requestBody = "f_mnuid=&f_uid=%@&f_pwd=%@&submit=%B5n%A4J"
var showLoginFailedAlert: ((String, String) -> Void)? = nil
var loginStateChanged: ((Bool) -> Void)? = nil
var loginSuccessfully: (() -> Void)? = nil
func login() {
loginState = true
var request = URLRequest(url: URL(string: loginURL)!)
request.httpMethod = "POST"
let cfEnc = CFStringEncodings.big5
let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(cfEnc.rawValue))
let big5encoding = String.Encoding(rawValue: nsEnc)
let body = String(format: requestBody, username, password)
request.httpBody = body.data(using: big5encoding)
URLSession.shared.dataTask(with: request) { data, response, error in
self.handleData(string: String(data: data!, encoding: big5encoding)!)
self.loginState = false
}.resume()
}
private func handleData(string data: String) {
print(data)
if data.contains("密碼錯誤") {
showLoginFailedAlert?("錯誤", "密碼錯誤,請再試一次")
} else if data.contains("帳號") && data.contains("不存在") {
showLoginFailedAlert?("錯誤", "帳號\(username)不存在")
} else { //login successfully
loginSuccessfully?()
}
}
}

View File

@ -1,25 +0,0 @@
//
// ViewController.swift
// StudyCK
//
// Created by secminhr on 2018/7/5.
// Copyright © 2018 secminhr. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}