写给iOS小白的MVVM教程(一): 从MVC到MVVM之一个典型的MVC应用场景
前言
本着实践为主的原则,此系列文章不做过多的概念性的阐述和讨论;更多的代码和篇幅用来展示MVC和MVVC下的基础代码结构与具体实现,来展示各自优劣.这篇文章,更多的在于发掘MVC与MVVC的共性,以期为那些对MVVC感兴趣的iOS开发者,找到一种平滑的过渡与重构代码的方式.如果对MVVC感兴趣,可以直接将本文的大部分代码引用到自己的项目中,毕竟代码是写出来的!开篇之前,你可以先到这里下载本文的示例工程: https://github.com/ios122/ios122
在这一篇章里,我会分别使用我所理解的MVC与MVVC两种模式来完成同一个应用场景,以期帮助那些熟悉传统MVC模式代码的iOS攻城狮,能更好理解MVVC.限于篇幅,将MVC和MVVM拆分为两个部分,今天要说的是一个典型的MVC的应用场景,为基于MVC的MVVM重构做个基础.这篇文章着重进行了接口准备,必须的知识点的说明等内容.
预设场景:按分类请求一组博客,点击获取博客详情
我们选取最常见的一组场景: 根据某种规则获取一组数据,点击某一条数据,可以跳转到下一界面获取数据详情.这里我会根据分类请求此分类下的博客列表,点击某一条信息,可跳转到博客详情页.简单说,其实我们真正需要实现的只有两个页面: 博客分类列表页 与 博客详情页.
数据接口准备
我们至少需要两个接口,一个可以根据分类来获取博客列表,一个用来根据id获取博客详情.
使用预定义的接口
如果你没有自己的服务器或者对服务器开发不熟悉,可以使用我准备的这两个测试接口:
博客列表接口
http://www.ios122.com/find_php/index.php?viewController=YFPostListViewController&model[category]=ui&model[page]=2
ui
分类名称,目前预定义支持:ui
,network
,tool
,autolayout
四个分类.2
,获取第几页的数据,从0开始计数,指请求此分类下第几页的数据.预定义每个分类下有100条数据,每20条数据一页.- 返回示例:
[
{
"id": "ui_40",
"title": "title_ui_40",
"desc": "desc_ui_40"
},
{
"id": "ui_41",
"title": "title_ui_41",
"desc": "desc_ui_41"
},
{
"id": "ui_42",
"title": "title_ui_42",
"desc": "desc_ui_42"
},
{
"id": "ui_43",
"title": "title_ui_43",
"desc": "desc_ui_43"
},
{
"id": "ui_44",
"title": "title_ui_44",
"desc": "desc_ui_44"
},
{
"id": "ui_45",
"title": "title_ui_45",
"desc": "desc_ui_45"
},
{
"id": "ui_46",
"title": "title_ui_46",
"desc": "desc_ui_46"
},
{
"id": "ui_47",
"title": "title_ui_47",
"desc": "desc_ui_47"
},
{
"id": "ui_48",
"title": "title_ui_48",
"desc": "desc_ui_48"
},
{
"id": "ui_49",
"title": "title_ui_49",
"desc": "desc_ui_49"
},
{
"id": "ui_50",
"title": "title_ui_50",
"desc": "desc_ui_50"
},
{
"id": "ui_51",
"title": "title_ui_51",
"desc": "desc_ui_51"
},
{
"id": "ui_52",
"title": "title_ui_52",
"desc": "desc_ui_52"
},
{
"id": "ui_53",
"title": "title_ui_53",
"desc": "desc_ui_53"
},
{
"id": "ui_54",
"title": "title_ui_54",
"desc": "desc_ui_54"
},
{
"id": "ui_55",
"title": "title_ui_55",
"desc": "desc_ui_55"
},
{
"id": "ui_56",
"title": "title_ui_56",
"desc": "desc_ui_56"
},
{
"id": "ui_57",
"title": "title_ui_57",
"desc": "desc_ui_57"
},
{
"id": "ui_58",
"title": "title_ui_58",
"desc": "desc_ui_58"
},
{
"id": "ui_59",
"title": "title_ui_59",
"desc": "desc_ui_59"
}
]
2.博客详情接口
http://www.ios122.com/find_php/index.php?viewController=YFPostViewController&model[id]=ui_0
ui_0
表示博客唯一标识.其应为分类博客列表返回的一个有效id.- 返回示例:
{
"title": "title of ui_0",
"body": "<h2>Hello iOS122</h2> Scann To Join Us <br /> <image alt=\"qq\" src=\"https://raw.githubusercontent.com/ios122/ios122/master/1443002712802.png\" />"
}
自定义接口
如果你有自己的服务器接口,直接使用即可;但是下面的oc代码,你可能也要对应变换下;如果你对服务器接口开发不是很了解,可以先阅读下这篇文章: iOS程序猿如何快速掌握 PHP,化身”全栈攻城狮”?.
假定,你已经阅读并领会了 << iOS程序猿如何快速掌握 PHP,化身”全栈攻城狮”? >>,这篇文章,新建问及那,并把下面的代码复制到对应文件中,然后根据自己的需要更改即可:
博客列表接口源文件
<?php // YFPostListViewController.php
class YFPostListViewController
{
public $model = array(); //!< 传入的数据.
private $countOfPerPage = 20; //!< 每页数据条数.
/* 获取内容,用于输出显示. */
protected function getContent()
{
/* 预定义一组数据 */
$datasource = array();
$categorys = array('ui', 'network', 'tool', 'autolayout');
for ($i=0; $i < count($categorys); $i++) {
$categoryName = $categorys[$i];
$categoryData = array();
for ($j=0; $j < 100; $j++) {
$item = array(
'id' => "{$categoryName}_{$j}",
'title' => "title_{$categoryName}_{$j}",
'desc' => "desc_{$categoryName}_{$j}"
);
$categoryData[$j] = $item;
}
$datasource[$categoryName] = $categoryData;
}
$queryCategoryName = $this->model['category'];
$queryPage = $this->model['page'];
$targetCategoryData = $datasource[$queryCategoryName];
$content = array();
for ($i = $this->countOfPerPage * $queryPage ; $i < $this->countOfPerPage * ($queryPage + 1); $i ++ ) {
$content[] = $targetCategoryData[$i];
}
$content = json_encode($content);
return $content;
}
public function show()
{
$content = $this->getContent();
header("Content-type: application/json");
echo $content;
}
}
博客详情接口源文件
<?php // YFPostViewController.php
class YFPostViewController
{
public $model = array(); //!< 传入的数据.
/* 获取内容,用于输出显示. */
protected function getContent()
{
$id = $this->model['id'];
$content = array(
'title' => "title of {$id}",
'body' => '<h2>Hello iOS122</h2> Scann To Join Us <br /> <image alt="qq" src="https://raw.githubusercontent.com/ios122/ios122/master/1443002712802.png" />'
);
$content = json_encode($content);
return $content;
}
public function show()
{
$content = $this->getContent();
header("Content-type: application/json");
echo $content;
}
}
MVC 版本实现: 类似的代码,你不知道敲过了多少遍
技术要点
下面列出将要用到的技术点,如有你不熟悉的,可点击对应链接访问:
- 使用 AFNetworking 来处理网络请求;
- 使用 MJExtension实现JSON到数据模型的自动转换;
- 使用 MJRefresh 实现下拉刷新与上拉加载更多的效果;
- 使用 Masonry 进行AutoLayout布局;
- 使用 MBProgressHUD 优化页面加载时的进度提示;
思路分析
博客分类列表页面:
- 在前一页面指定博客分类;
- 页面加载时自动发起网络请求获取对应分类的数据;
- 获取数据成功后,自动刷新视图;获取失败,则给出错误提示;
- 点击某一条数据,可跳转到博客详情页.
博客详情页面:
- 在前一页面指定博客id;
- 页面加载时自动发起网络请求获取id的博客详情;
- 获取成功后,自动刷新视图;获取失败,则给出错误提示.
博客列表页面
1. 在前一页面指定博客分类;
这一步,大家肯定都会:
YFMVCPostListViewController * mvcPostListVC = [[YFMVCPostListViewController alloc] init];
mvcPostListVC.categoryName = @"ui";
[self.navigationController pushViewController: mvcPostListVC animated: YES];
2. 页面加载时自动发起网络请求获取对应分类的数据;
为了保证每次都能进入列表页,都能自动刷新数据,建议在 viewWillAppear:
方法刷新数据:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
[self updateData];
}
updateData
方法进行数据的更新:
- (void)updateData
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString * urlStr = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostListViewController&model[category]=%@&model[page]=0", self.categoryName];
[manager GET: urlStr parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
}
此处使用的是预定义接口,数据请求成功后,控制台输入如下:
JSON: (
{
desc = "desc_ui_0";
id = "ui_0";
title = "title_ui_0";
},
{
desc = "desc_ui_1";
id = "ui_1";
title = "title_ui_1";
},
{
desc = "desc_ui_2";
id = "ui_2";
title = "title_ui_2";
},
{
desc = "desc_ui_3";
id = "ui_3";
title = "title_ui_3";
},
{
desc = "desc_ui_4";
id = "ui_4";
title = "title_ui_4";
},
{
desc = "desc_ui_5";
id = "ui_5";
title = "title_ui_5";
},
{
desc = "desc_ui_6";
id = "ui_6";
title = "title_ui_6";
},
{
desc = "desc_ui_7";
id = "ui_7";
title = "title_ui_7";
},
{
desc = "desc_ui_8";
id = "ui_8";
title = "title_ui_8";
},
{
desc = "desc_ui_9";
id = "ui_9";
title = "title_ui_9";
},
{
desc = "desc_ui_10";
id = "ui_10";
title = "title_ui_10";
},
{
desc = "desc_ui_11";
id = "ui_11";
title = "title_ui_11";
},
{
desc = "desc_ui_12";
id = "ui_12";
title = "title_ui_12";
},
{
desc = "desc_ui_13";
id = "ui_13";
title = "title_ui_13";
},
{
desc = "desc_ui_14";
id = "ui_14";
title = "title_ui_14";
},
{
desc = "desc_ui_15";
id = "ui_15";
title = "title_ui_15";
},
{
desc = "desc_ui_16";
id = "ui_16";
title = "title_ui_16";
},
{
desc = "desc_ui_17";
id = "ui_17";
title = "title_ui_17";
},
{
desc = "desc_ui_18";
id = "ui_18";
title = "title_ui_18";
},
{
desc = "desc_ui_19";
id = "ui_19";
title = "title_ui_19";
}
)
3. 获取数据成功后,自动刷新视图;获取失败,则给出错误提示;
这一部分,涉及的变动较多,我就直接贴代码了.你会注意到View和数据已经交叉进行了,很乱的感觉.而这也是我们想要使用MVVM重构代码的重要原因之一.
//
// YFMVCPostListViewController.m
// iOS122
//
// Created by 颜风 on 15/10/14.
// Copyright (c) 2015年 iOS122. All rights reserved.
//
#import "YFMVCPostListViewController.h"
#import "YFArticleModel.h"
#import <AFNetworking.h>
#import <MJRefresh.h>
#import <MBProgressHUD.h>
@interface YFMVCPostListViewController ()<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView * tableView;
@property (nonatomic, strong) NSMutableArray * articles; //!< 文章数组,内部存储AFArticleModel类型.
@property (assign, nonatomic) NSInteger page; //!< 数据页数.表示下次请求第几页的数据.
@end
@implementation YFMVCPostListViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (NSMutableArray *)articles
{
if (nil == _articles) {
_articles = [NSMutableArray arrayWithCapacity: 42];
}
return _articles;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
// 马上进入刷新状态
[self.tableView.header beginRefreshing];
}
- (UITableView *)tableView
{
if (nil == _tableView) {
_tableView = [[UITableView alloc] init];
[self.view addSubview: _tableView];
[_tableView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
}];
_tableView.delegate = self;
_tableView.dataSource = self;
NSString * cellReuseIdentifier = NSStringFromClass([UITableViewCell class]);
[_tableView registerClass: NSClassFromString(cellReuseIdentifier) forCellReuseIdentifier:cellReuseIdentifier];
_tableView.header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
self.page = 0;
[self updateData];
}];
_tableView.footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
[self updateData];
}];
}
return _tableView;
}
/**
* 更新视图.
*/
- (void) updateView
{
[self.tableView reloadData];
}
/**
* 更新数据.
*
* 数据更新后,会自动更新视图.
*/
- (void)updateData
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString * urlStr = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostListViewController&model[category]=%@&model[page]=%ld", self.categoryName, (long)self.page ++];
[manager GET: urlStr parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.tableView.header endRefreshing];
[self.tableView.footer endRefreshing];
if (1 == self.page) { // 说明是在重新请求数据.
self.articles = nil;
}
NSArray * responseArticles = [YFArticleModel objectArrayWithKeyValuesArray: responseObject];
[self.articles addObjectsFromArray: responseArticles];
[self updateView];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.tableView.header endRefreshing];
[self.tableView.footer endRefreshing];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeText;
hud.labelText = @"您的网络不给力!";
[hud hide: YES afterDelay: 2];
}];
}
# pragma mark - tabelView代理方法.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger number = self.articles.count;
return number;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * cellReuseIdentifier = NSStringFromClass([UITableViewCell class]);
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: cellReuseIdentifier forIndexPath:indexPath];
YFArticleModel * model = self.articles[indexPath.row];
NSString * content = [NSString stringWithFormat: @"标题:%@ 内容:%@", model.title, model.desc];
cell.textLabel.text = content;
return cell;
}
@end
4. 点击某一条数据,可跳转到博客详情页.
只需要再额外实现下 -tableView: didSelectRowAtIndexPath:
方法即可:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 跳转到博客详情.
YFArticleModel * articleModel = self.articles[indexPath.row];
YFMVCPostViewController * postVC = [[YFMVCPostViewController alloc] init];
postVC.articleID = articleModel.id;
[self.navigationController pushViewController: postVC animated: YES];
}
博客详情页面
1. 在前一页面指定博客id;
这里其实就是博客列表的控制器的那几句:
// 跳转到博客详情.
YFArticleModel * articleModel = self.articles[indexPath.row];
YFMVCPostViewController * postVC = [[YFMVCPostViewController alloc] init];
postVC.articleID = articleModel.id;
[self.navigationController pushViewController: postVC animated: YES];
2. 页面加载时自动发起网络请求获取id的博客详情;
此处为了方便,我们依然使用预定义的博客详情接口:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString * urlStr = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostViewController&model[id]=%@", self.articleID];
[manager GET: urlStr parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@", responseObject);
[self updateView];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeText;
hud.labelText = @"您的网络不给力!";
[hud hide: YES afterDelay: 2];
}];
请求的输入,Xcode控制台打印输出,类似于:
{
body = "<h2>Hello iOS122</h2> Scann To Join Us <br /> <image alt=\"qq\" src=\"https://raw.githubusercontent.com/ios122/ios122/master/1443002712802.png\" />";
title = "title of ui_0";
}
3. 获取成功后,自动刷新视图;获取失败,则给出错误提示.
你会注意到,我们在上一步获取的数据,body部分内部是HTML字符串,所以我们要使用webView来显示博客详情.这和最近炒得很火的的混合开发模式有些像,但是目前主流的博客应用,几乎都是这么做的.完整代码如下:
//
// YFMVCPostViewController.m
// iOS122
//
// Created by 颜风 on 15/10/16.
// Copyright (c) 2015年 iOS122. All rights reserved.
//
#import "YFMVCPostViewController.h"
#import "YFArticleModel.h"
#import <AFNetworking.h>
#import <MBProgressHUD.h>
@interface YFMVCPostViewController ()<UIWebViewDelegate>
@property (strong, nonatomic) UIWebView * webView;
@property (strong, nonatomic) YFArticleModel * article;
@end
@implementation YFMVCPostViewController
- (UIWebView *)webView
{
if (nil == _webView) {
_webView = [[UIWebView alloc] init];
[self.view addSubview: _webView];
[_webView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(UIEdgeInsetsMake(64, 0, 0, 0));
}];
}
return _webView;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
[self updateData];
}
/**
* 更新视图.
*/
- (void) updateView
{
[self.webView loadHTMLString: self.article.body baseURL:nil];
}
/**
* 更新数据.
*
* 数据更新后,会自动更新视图.
*/
- (void)updateData
{
[MBProgressHUD showHUDAddedTo:self.view animated: YES];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString * urlStr = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostViewController&model[id]=%@", self.articleID];
[manager GET: urlStr parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.article = [YFArticleModel objectWithKeyValues: responseObject];
[self updateView];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeText;
hud.labelText = @"您的网络不给力!";
[hud hide: YES afterDelay: 2];
}];
}
@end
小结
此篇主要展示了一个典型的列表-->详情场景的MVC实现,相关技术代码可以直接用于自己的项目中.尽管这是简化的场景,但依然可以很明显地看出来数据,网络请求与视图间的相互调用,使代码整体的可复用性大大降低! 而这,也是我们下次要用 MVVC 重构这个示例的核心目的之一!
写给iOS小白的MVVM教程(一): 从MVC到MVVM之一个典型的MVC应用场景的更多相关文章
- 写给iOS小白的MVVM教程(序)
这几天,需要重构下部分代码,这里简要记录下.但是涉及的技术要点还是很多,所以分为多个篇章叙述.此教程来源于,并将于应用于实践,不做过多的概念性阐释和争论.每个篇章都会附上实际的可执行的代码.因涉及的技 ...
- iOS 关于MVC和MVVM设计模式的那些事
一.概述 在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,是苹果推荐的一个用来组织代码的权威范式.Apple甚至是这么说的.在MVC下,所有的对 ...
- (翻译)开始iOS 7中自动布局教程(二)
这篇教程的前半部分被翻译出来很久了,我也是通过这个教程学会的IOS自动布局.但是后半部分(即本篇)一直未有翻译,正好最近跳坑翻译,就寻来这篇教程,进行翻译.前半部分已经转载至本博客,后半部分即本篇.学 ...
- (译)cocos2d-x跨android&ios平台开发入门教程
免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播.同时,转载时不要移除本申明.如产生任何纠纷,均与本博客所有人.发表该翻译稿之人无任何关系.谢谢合作 ...
- 微信iOS消息拦截插件教程-Tweak HelloWorld
微信iOS消息拦截插件教程-Tweak HelloWorld 标签(空格分隔): 越狱开发教程 1.环境准备 准备一台越狱的手机,具体参照上一篇教程 搭建Theos越狱开发环境 2.开发过程 新建一个 ...
- Sagit.Framework For IOS 开发框架入门开发教程2:一行代码实现引导页
前言: 开篇比较简单:Sagit.Framework For IOS 开发框架入门开发教程1:框架下载与环境配置 第二篇教程之前写了一半,感觉不太好写,而且内容单纯介绍API,要说的很多,又枯燥乏味. ...
- iOS 写给iOS开发者的React Native学习路线(转)
我是一名iOS开发者,断断续续一年前开始接触React Native,最近由于工作需要,专职学习React Native也有一个多月了.网络上知识资源非常的多,但能让人豁然开朗.迅速学习的还是少数,我 ...
- iOS 9应用开发教程之多行读写文本ios9文本视图
iOS 9应用开发教程之多行读写文本ios9文本视图 多行读写文本——ios9文本视图 文本视图也是输入控件,与文本框不同的是,文本视图可以让用户输入多行,如图2.23所示.在此图中字符串“说点什么吧 ...
- iOS 9应用开发教程之显示编辑文本标签文本框
iOS 9应用开发教程之显示编辑文本标签文本框 ios9显示.编辑文本 在iOS,经常会看到一些文本的显示.文字就是这些不会说话的设备的嘴巴.通过这些文字,可以很清楚的指定这些设备要表达的信息.本节将 ...
随机推荐
- Broken Keyboard (a.k.a. Beiju Text) UVA - 11988 (链表)
题目链接:https://vjudge.net/problem/UVA-11988 题目大意:输入一个字符串,输出在原本应该是怎么样的? 具体方法是 碰到' [ ' 回到最前面 碰到‘ ]’ 回 ...
- (转)Shell脚本之break,continue,和exit区别
Linux脚本中的break continue exit return break结束并退出循环 continue在循环中不执行continue下面的代码,转而进入下一轮循环 exit退出脚本,常带一 ...
- 关于java中char占几个字节,汉字占几个字节
我们平常说,java中char占2个字节,可又说汉字在不通的编码格式中所占的位数是不同的,比如gbk中汉字占2个字节,utf8中多数占3个字节,少数占4个.而所有汉字在java程序中我们都可以简单的用 ...
- DEDE把变量放进session中,结果取值为null的问题
最近在基于织梦CMS(dedecms)做公司网站,可以说改动不少,而其中最令我印象深刻的就是织梦的session. 自己想在前台页面限制一些用户的访问,且后台用户可以访问.必须验证织梦后台用户的 ...
- 【input】——数据传入后台
1.复选框 checkbox <label class="checkbox"> <input type="checkbox" name=&qu ...
- Ajax提交表单数据(包含文件)
1. 表单数据->JSON->后台 2. 表单序列化[方式一] jquery.serializejson.js <script src="/js/jquery.serial ...
- Android自定义控件练手——波浪效果
这一次要绘制出波浪效果,也是小白的我第一次还望轻喷.首先当然是展示效果图啦: 一.首先来说说实现思路. 想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在 ...
- 数据批量删除_从页面js到后台数据库
LayUI 批量选择的 ,然后操作 batchdel: function () { var checkStatus = table.checkStatus('LAY-gridview') , ch ...
- 从PeopleEditor控件中取出多用户并更新到列表
如果一个列表中有一个字段类型为用户或用户组,并且设置为用户,允许多值的话,那么用代码进行更新的时候就必须将这个字段的值赋成SPFieldUserValueCollection类型,以下代码即为从Peo ...
- 使用sqlyog连接ubuntu mysql server错误解决方案
现在很多服务都部署在linux环境中,但是在开发阶段,使用windows远程连接工具,直观,这对开发人员更友好. 下面是我在ubuntu16.04使用mysql- server时,遇到了一下的问题,以 ...