最近花了幾天時(shí)間研究了一下怎么用iOS原生頁(yè)面高效的更新app頁(yè)面樣式,我的思路是使用仿H5樣式的JSON數(shù)據(jù)做个,利用各種布局(流動(dòng)鸽心,線性等布局)來(lái)動(dòng)態(tài)更新頁(yè)面樣式滚局。
頁(yè)面動(dòng)態(tài)化主要的使用場(chǎng)景是app的電商頁(yè)面亮垫,由于電商的業(yè)務(wù)需求較大等限,經(jīng)常會(huì)需要改變各個(gè)banner的樣式來(lái)滿足業(yè)務(wù)的需求弧腥,所以需要客戶端根據(jù)數(shù)據(jù)實(shí)時(shí)更新樣式军浆,下面說(shuō)說(shuō)我對(duì)頁(yè)面動(dòng)態(tài)化的理解跟思路愧旦。先看一下最后demo的效果:
其他app是怎么做的
用Charles抓包左胞,發(fā)現(xiàn)京東跟網(wǎng)易考拉都是用type這個(gè)字段提前定義好各種banner的樣式魏身,通過(guò)后臺(tái)返回的數(shù)據(jù)刀脏,來(lái)實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)化更新蟀淮,這樣做有好的方面也有不好的地方:
好的方面:提前定義好banner的樣式最住,客戶端只需要定義好各個(gè)樣式的UI,根據(jù)type字段來(lái)展現(xiàn)就好
不好的方面:只能根據(jù)定義好的樣式來(lái)進(jìn)行動(dòng)態(tài)化更新怠惶,如果需要加入新的樣式涨缚,需要提交新包
我的做法
客戶端不定義banner樣式,而是定義布局樣式來(lái)實(shí)現(xiàn)高度動(dòng)態(tài)更新頁(yè)面策治,思路來(lái)自于CSS里定義的div+CSS布局脓魏,CSS里可以根據(jù)display的inline跟block值來(lái)進(jìn)行布局,iOS里通惫,我定義了幾種常見(jiàn)的布局:
FlowLayout:流式布局茂翔,根據(jù)display值橫向或者縱向布局,根據(jù)屏幕寬度跟banner寬度換行
LinearLayout:線性布局讽膏,縱向布局
WaterfallLayout:瀑布流布局
OneAndNLayout:左邊一個(gè)大圖檩电,右邊N張小圖的布局
StickyLayout:懸浮布局
整體架構(gòu)
頁(yè)面動(dòng)態(tài)化 整體架構(gòu)如下:
每個(gè)UIViewController都是一個(gè)頁(yè)面,頁(yè)面是由一個(gè)個(gè)卡片組成的府树,卡片其實(shí)就是代表了一個(gè)布局樣式俐末,每個(gè)卡片是由一個(gè)個(gè)元素組成的,元素其實(shí)指的就是卡片布局樣式下的各個(gè)banner奄侠,對(duì)應(yīng)的JSON數(shù)據(jù)結(jié)構(gòu)如下:
{
"cards":
[
{
"layout": 1,
"elements": [
{
},
{
},
...
]
},
{
"layout": 1,
"elements": [
{
},
{
},
...
]
},
...
]
}
具體實(shí)現(xiàn)
由于大部分頁(yè)面內(nèi)容都需要上下滑動(dòng)顯示更多內(nèi)容的卓箫,所以頁(yè)面動(dòng)態(tài)化的實(shí)現(xiàn)是基于UICollectionView來(lái)做的,當(dāng)然也可以使用UIScrollView來(lái)實(shí)現(xiàn)(淘寶天貓都是通過(guò)一個(gè)自定義具有回收復(fù)用機(jī)制的UIScrollView來(lái)展示動(dòng)態(tài)化頁(yè)面內(nèi)容的)垄潮,我的理解是既然UICollectionView可以通過(guò)自定義UICollectionViewLayout來(lái)展現(xiàn)自定義的cell烹卒,為什么不用還需要自己定義一個(gè)具有回收復(fù)用機(jī)制的UIScrollView去展現(xiàn)內(nèi)容,UICollectionView自身就是帶有回收復(fù)用機(jī)制的弯洗。我的具體實(shí)現(xiàn)旅急,先看下整體的UML圖:
每個(gè)KBBaseCard代表了一個(gè)布局的卡片,對(duì)應(yīng)UICollectionView里的一個(gè)section牡整,KBBaseCard里elements的每個(gè)KBBaseElement代表了一個(gè)banner藐吮,對(duì)應(yīng)UICollectionView里的一個(gè)item,KBBaseElement里的KBElementStyle提供banner需要展現(xiàn)的樣式。KBElementStyle里的type字段是用來(lái)定義banner的樣式谣辞,比方說(shuō)type=1代表這個(gè)banner就是一張圖迫摔。實(shí)際展示圖如下:
UICollectionViewLayout的自定義
UICollectionView提供的UICollectionViewFlowLayout并不能滿足我的對(duì)樣式展現(xiàn)的需求,所以這里我們必須自己寫一套UICollectionViewLayout來(lái)提供對(duì)KBBaseElement里style字段里的樣式支持泥从,核心方法如下句占,具體實(shí)現(xiàn)在本文中不做過(guò)多詳解,大家可以自己查詢一下相關(guān)的知識(shí):
-
(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];NSInteger sectionLayout = 1; // CardFlowLayout is the default layout
if (self.delegate && [self.delegate respondsToSelector:@selector(collectionView:layout:layoutForSection:)]) {
sectionLayout = [self.delegate collectionView:self.collectionView layout:self layoutForSection:indexPath.section];
}// CardFlowLayout 流式布局
if (sectionLayout == 1) {
[self handleLayoutAttributesForFlowLayout:layoutAttributes atIndexPath:indexPath];
}
// CardLinearLayout 線性布局
if (sectionLayout == 2) {
[self handleLayoutAttributesForLinearLayout:layoutAttributes atIndexPath:indexPath];
}
// CardOneAndNLayout 左邊一張大圖右邊N張小圖布局
if (sectionLayout == 3) {
[self handleLayoutAttributesForOneAndNLayout:layoutAttributes atIndexPath:indexPath];
}
// CardWaterfallLayout 瀑布流布局
if (sectionLayout == 4) {
[self handleLayoutAttributesForWaterfallLayout:layoutAttributes atIndexPath:indexPath];
}return layoutAttributes;
}
JSON數(shù)據(jù)
這里提供一個(gè)我自己創(chuàng)建的JSON mock數(shù)據(jù)躯嫉,還有根據(jù)這個(gè)JSON數(shù)據(jù)最終展現(xiàn)出來(lái)的頁(yè)面圖
JSON
{
"cards": [
{
"layout": 1,
"style": {
"backgroundColor": "#ffffff"
},
"elements": [
{
"type": 1,
"image": "http://haitao.nos.netease.com/lqdbpar1xWZw5NuOB9ezf80UDm75Ei%2BZyMo%2BMgvE%2Bdunea_02T1704271119_480_240.jpg",
"style": {
"display": "inline",
"margin": [
"0",
"0",
"0.75",
"0.75"
],
"width_ratio": 0.5,
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/i11BQFE2dWpzplHNBV1Tr8Hafa5zTz%2BCoxF%2BwIBS%2BKF2Vx_03T1704261946_480_240.jpg",
"style": {
"display": "inline",
"margin": [
"0",
"0.75",
"0.75",
"0"
],
"width_ratio": 0.5,
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/KwaBThC4mguSnQCGBXVtfK4RmTTSeQ%2BZ5Ql%2BTNvQ%2BKNWzP_05T1704251128_480_240.jpg",
"style": {
"display": "inline",
"margin": [
"0.75",
"0",
"0",
"0.75"
],
"width_ratio": 0.5,
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/G4LGkdg7si4p5ZzVBZUKmkTq7EeTKt%2BRRaE%2BfQyx%2B6JlfC_06T1704251128_480_240.jpg",
"style": {
"display": "inline",
"margin": [
"0.75",
"0.75",
"0",
"0"
],
"width_ratio": 0.5,
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/HNkdnuFcFey5Om5GpNpO-C2LLgTrO3WtFT1704201935_960_210.jpg",
"style": {
"display": "block",
"margin": [
"0.75",
"0",
"0",
"0"
],
"width_height_ratio": 4.57
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/mrui23TlZJcRAocRWB70T1704262151_600_375.jpg",
"style": {
"display": "inline",
"margin": [
"0.75",
"0",
"0",
"0.75"
],
"width_ratio": 0.7,
"width_height_ratio": 1.6
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/ppiV8ODszIZciIZbmQih-A6HUakFq6g6oLTNhT1704201914_474_555.jpg",
"style": {
"display": "inline",
"margin": [
"0.75",
"0.75",
"0",
"0"
],
"width_ratio": 0.3,
"width_height_ratio": 0.685
}
}
]
},
{
"layout": 3,
"style": {
"backgroundColor": "#ffffff"
},
"elements": [
{
"type": 1,
"image": "http://haitao.nos.netease.com/KHASB80nO5JMsX1e2iChshdRT1704262150_480_480.jpg",
"style": {
"margin": [
"0",
"0",
"0",
"0.75"
],
"width_ratio": 0.5,
"width_height_ratio": 1.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/ygMuXb96hrtGtT6Wc9vZ2T17040101959_480_240.jpg",
"style": {
"margin": [
"0",
"0.75",
"0.75",
"0"
],
"width_ratio": 0.5,
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/ADB5IaeoJWP9pIzVmwzkfGKhT1704262151_480_240.jpg",
"style": {
"margin": [
"0.75",
"0.75",
"0",
"0"
],
"width_ratio": 0.5,
"width_height_ratio": 2.0
}
}
]
},
{
"layout": 1,
"style": {
"backgroundColor": "#ffffff"
},
"elements": [
{
"type": 1,
"image": "http://haitao.nos.netease.com/ppiV8ODszIZciIZbmQih-A6HUakFq6g6oLTNhT1704201914_474_555.jpg",
"style": {
"display": "inline",
"margin": [
"0",
"0",
"0",
"0.75"
],
"width_ratio": 0.5,
"width_height_ratio": 0.856
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/mbO7ln3VxpAWxKkw84Ay-ys6BLHmk9LLRvwa4T1704201914_474_555.jpg",
"style": {
"display": "inline",
"margin": [
"0",
"0.75",
"0",
"0"
],
"width_ratio": 0.5,
"width_height_ratio": 0.856
}
}
]
},
{
"layout": 2,
"style": {
"backgroundColor": "#ffffff"
},
"elements": [
{
"type": 1,
"image": "http://haitao.nos.netease.com/hwCkTs9AoDThXhv5k38dr2M0T1704242204_960_480.jpg",
"style": {
"margin": [
"0",
"0",
"10",
"0"
],
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/Tt9TfxyDTSSirP27lXEiIsKPriT1704241519_960_480.jpg",
"style": {
"margin": [
"0",
"0",
"10",
"0"
],
"width_height_ratio": 2.0
}
},
{
"type": 1,
"image": "http://haitao.nos.netease.com/uey1sJ03lEOglRm626Th6dKrT1704242203_960_480.jpg",
"style": {
"margin": [
"0",
"0",
"10",
"0"
],
"width_height_ratio": 2.0
}
}
]
}
]
}
頁(yè)面圖