使用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