1. 程式人生 > >使用MMVVC模式和JSAppSugar將iOS原生應用UI層業務邏輯轉為JavaScript

使用MMVVC模式和JSAppSugar將iOS原生應用UI層業務邏輯轉為JavaScript

JSAUIKitCocoa是為使用JavaScript混合Objective-C開發iOS應用提供的MVC框架,以及為部分原生UI元件(如UIView)提供JavaScript快速初始化支援。使用JSAUIKitCocoa,你可以方便的使用JavaScript來編寫iOS應用的顯示層業務邏輯,以及實現顯示層業務邏輯的動態下發。

核心概念

使用MMVVC模式構建混合程式設計應用程式

我們將用JavaScript和MMVVC模式建立這樣一個應用介面

使用者進入登入頁面時,顯示最後一次登入的使用者資訊,使用者修改使用者名稱後,頭像中的名稱顯示當前使用者名稱(真實場景應該會改變頭像圖片)

Model:使用者管理

$class("app.UserManagerModel",{
	$static:{
		lastloginUser:function(){
			return {
				username:"UserA",
				password:"password"
			}
		}
	}
});

lastloginUser方法返回最後一次登入的使用者資訊,即Domain-value Object。

ModelView:使用者頭像

$class("app.AvatarView",{
	$init : function(param){
		this.textView = $new("UILabel","initWithJSAParam:",{
			fontSize:12,
			textColor:"#FFFFFF",
			textAlignment:"center"
		});
		var avatarView = $new("MyRelativeLayout","initWithJSAParam:",Object.assign(param,{
			backgroundColor:"#03A9F4",
			subviews:[
			{
				view:this.textView,
				leftPos:5,
				topPos:5,
				rightPos:5,
				bottomPos:5,
			}
			]
		}));
		//這裡是一個小技巧,將一個非OC代理物件轉換為OC代理物件,以便該物件傳遞到OC系統時轉換為OC物件
		this.$this = avatarView.$this;
	},
	setName:function(name){
		name = name.trim();
		if(name == ""){
			name = "Unknown";
		}
		this.textView.invoke("setText:",name);
	}
});

使用者頭像是一個ModelView,即和業務邏輯相關的View,這個View將顯示使用者頭像,並可以重新設定使用者名稱(setName方法),setName方法在此DEMO實現中只是將頭像中的文字設定為新的名字,真實業務場景可以呼叫Model層方法獲取使用者名稱對應的圖片,然後修改頭像圖片。相比ViewMode的Data-Bind,ModelView可以包含更加複雜的顯示轉換邏輯。同時這個ModelView可以複用到任何需要展示使用者頭像的地方。

使用OC編寫ModelView

如果這個ModelView的顯示邏輯更為複雜,如處理複雜動畫顯示,則這個ModelView可直接使用OC編寫,即編寫一個UIView的繼承類,在這個例子中,AvatarView實際是一個MyRelativeLayout(MyLayout框架中的相對佈局UIView)物件,AvatarView使用JS指令碼編寫顯示業務邏輯,在MyRelativeLayout中放入了一個UILabel以顯示使用者名稱文字。

ModelView中使用的OC原生類,如MyRelativeLayout、UILabel,因為其與業務邏輯無關,則可視為View層元件。

ModelView:登入介面

$class("app.LoginView",{
	$init : function(lastloginUser){
		this.avatarView = new app.AvatarView({
			width:80,height:80
		});
		this.avatarView.setName(lastloginUser.username);
		this.userNameInput = $new("UITextField","initWithJSAParam:",{
			width:200,height:30,borderStyle:"RoundedRect",
			placeholder:"使用者名稱",
			text:lastloginUser.username,
			onEditingDidEnd:function(view){
				jsa.cocoa.UIResponder.fromNative(view).dispatch("onEditingDidEnd",null,null);
			}
		});
		this.passwordInput = $new("UITextField","initWithJSAParam:",{
			width:200,height:30,borderStyle:"RoundedRect",secureTextEntry : true,
			placeholder:"密碼",
			text:lastloginUser.password,
		});
		var loginView = $new("MyRelativeLayout","initWithJSAParam:",{
			subviews:[
			{
				id : "avatar",
				view:this.avatarView,
				topPos :{value : "safeAreaMargin",offset : 20},
				centerXPos : 0,
			},
			{
				id:"username",
				view:this.userNameInput,
				topPos :{id : "avatar",pos : "bottomPos",offset : 10,},
				centerXPos : 0,
			},
			{
				id:"password",
				view:this.passwordInput,
				topPos :{id : "username",pos : "bottomPos",offset : 10,},
				centerXPos : 0,
			},{
				view:jsa.cocoa.UIButton.button({
					type:"System",
					width:50,
					height:30,
					title:"登入",
					onClick:function(view){
						jsa.cocoa.UIResponder.fromNative(view).dispatch("onLoginClicked",null,null);
					}
				}),
				topPos :{id : "password",pos : "bottomPos",offset : 10,},
				centerXPos : 0,
			}
			]
		});
		jsa.cocoa.UIResponder.fromNative(loginView.$this).setObserver(this);
		this.$this = loginView.$this;
	},
	onEditingDidEnd:function(){
		//在使用者名稱輸入結束後,將新的使用者名稱更新到avatarView
		var username = this.userNameInput.invoke("text");
		this.avatarView.setName(username);
	},
	onLoginClicked:function(){
		//當點選登入按鈕時,發出登入事件,並在事件物件中加入登入表單物件的值(類似於form的post)
		var username = this.userNameInput.invoke("text");
		var password = this.passwordInput.invoke("text");
		//丟擲事件,Controller可監聽該事件以處理登入事件
		jsa.cocoa.UIResponder.fromNative(this.$this).dispatch("onLogin",null,{
			username:username,
			password:password
		});
	}
});

LoginView即登入頁面,在LoginView中使用app.AvatarView來展示使用者頭像,並監聽UITextField元件的onEditingDidEnd事件,以動態改變登入使用者名稱對應的使用者頭像。通過引入ModelView,可有效的將顯示業務中的臨時互動邏輯放在ModelView中,而避免將顯示邏輯程式碼寫在Controller中。

Controller:登入頁面

$class("app.Main",{
	$extends : "jsa.cocoa.JSAUIViewController",
	getView : function(viewController){
		var lastloginUser = app.UserManagerModel.lastloginUser();
		this.loginView = new app.LoginView(lastloginUser);
		return this.loginView;
	},
	onLogin:function(object,userInfo){
		console.log("User:"+userInfo.username+" Password:"+userInfo.password);
	}
});

在將顯示邏輯分離到ModelView後,Controller的職責就更加清晰了,getView方法中獲得頁面所需的Model資料,並用Model資料初始化頁面對應的ModelView物件。

onLogin方法處理loginView檢視中的登入事件。

該DEMO的可執行版本已包含在JSAUIKitCocoaDemo,JS指令碼地址:https://github.com/JSAppSugar/JSAUIKitCocoa/tree/master/JSAUIKitCocoaDemo/JSApp/app