iOS回顾笔记( 02 ) -- 由九宫格布局引发的一系列“惨案”

 

iOS回顾笔记( 02 ) -- 由九宫格布局引发的一系列“惨案”

前言(扯几句淡先)

回顾到学习UI过程中的九宫格布局时,发现当时学的东西真是不少。

这个阶段最大的特点就是:知识点繁多且琐碎

我们的目标就是要将这琐碎的知识点灵活运用、融汇贯通,通过不同的实现方式来实现相同的功能,最后进行比较得到最好的那种方式。这个求知的过程就是我们最需要学习的,在过程中我们学会了自我思考,并且在自己的思考和比较中,我们的脑海里逐渐形成了自己的编程思想。

本文主要以九宫格购物车的实现为引子,从最基础的实现方法层层递进直到最完美的实现方式。代码从起初的低效、耦合度高到后期的层层分离、MVC各模块封装,高内聚、低耦合,更具扩展性等方面逐步深化和扩展;经过编程思想的层层深入和代码的步步完善最后完整的展现在用户面前。

本文大体目录

  • 九宫格购物车demo
  • 对应物品封装(View的简单封装)
  • 数据的加载方式对比 与 演变
  • 属性列表文件(plist)的创建和使用
  • MVC 思想的引入和介绍

九宫格购物车Demo

九宫格是我们在开发过程中对于一些有规律的UI布局常用的一种布局算法。
九宫格特点:简单,封装性好,可复用性高,很适合一些页面同类型item数量动态变化UI页面布局

下面就实现一个买书购物车

Snip20170301_3.png
基本要求和思路如下

思路:
1. 最初没有书(删除按钮不可用)
2. 点击添加就添加一本书(删除按钮可用)
3. 添加基本之后(提示购物车已满,添加按钮不可用)
4. 删除按钮点击(购物车不满的时候添加按钮又可以使用)
5. 添加书籍按九宫格的样式在页面中显示




先说说最直接的方法

// 添加书
- (IBAction)addBook:(id)sender {// 1. 创建书图标UIImageView *bookIcon = [UIImageView new];bookIcon.image = [UIImage imageNamed:@"0"];bookIcon.frame = CGRectMake(0, 0, 50, 50);[self.shopView addSubview:bookIcon];// 2.书名UILabel *bookName = [UILabel new];bookName.frame = CGRectMake(0, 50, 50, 20);bookName.text = @"book1";bookName.textAlignment = NSTextAlignmentCenter;[self.shopView addSubview:bookName];// 3.添加到数组(分开写好像很难明确如何添加这本书)}

这是最直接的方法来添加的一本书,这样确实能添加一本书,但是这种直观、死板的思想是不对的。

  • 面对这样的动态变化的UI页面,每次添加和删除书籍的操作都是用户随机的,所以代码每次也是根据对应的点击来计算对应书籍要添加的位置。
  • 每本书的位置主要是和它对应的index来确定,这就涉及到“书”这个对象要每次计数,书是一个整体,所以书内部的东西应当封装起来。
  • “书”在UI上表现是 图标 + 书名,也就是iamgeview + label,从用户看到的整体性上来说,每次添加和删除同一本书也需要对 图标 和 书名 分别计算两次来计算和排列。这也是非常不合理的,并且很容易计算出现问题。应该根据UIView 父子控件相关特性对“书”进行封装,添加/删除的时候统一处理父控件,至于内部属性都会根据父控件来自动布局,方便管理。

书的封装

了解以上说的直接把代码分开写的局限性之后,现在来封装一下“书”这个对象。

  • UI层的封装,我们先分析UI布局:内部属性只有 图标(UIImagevView) 和 书名(UILabel)
  • 父控件选取原则:父控件只是承载子控件的容器,应当简洁为主,所以选择 UIView
// 0. 创建书UIView *book = [UIView new];book.frame = CGRectMake(0, 0, 60, 70);book.backgroundColor = [UIColor redColor];[self.shopView addSubview:book];// 1. 创建书图标UIImageView *bookIcon = [UIImageView new];bookIcon.image = [UIImage imageNamed:@"0"];bookIcon.frame = CGRectMake(0, 0, 60, 50);[book addSubview:bookIcon];// 2.书名UILabel *bookName = [UILabel new];bookName.frame = CGRectMake(0, 50, 60, 20);bookName.text = @"book1";bookName.textAlignment = NSTextAlignmentCenter;[book addSubview:bookName];// 3.添加到数组(直接添加书这个对象)[self.books addObject:book];

这样在创建和管理书的时候就方便多了,并且有一个数组来记录添加书的数量,在添加和删除的时候有计算的依据:可计算对应位置和两个按钮的可用情况

九宫格布局的思路和实现

书的对象已经封装好了,我们可以以整体思维来操作它,下面就是计算位置的思路。

  • 书的数量不定,但列数是固定的,可以设置成变量 int clos = 3;
  • 每本书之间可能有一定间距,横向间距 margin = ( width - clos * book.width)/ ( clos / 2) ;
  • 每本书的位置(x,y)可根据下图发现规律 x = 列号 * (W + margin); y = 行号 * (H+margin)
  • 行号规律 : 行号 = index / clos ;
  • 列号规律 : 列号 = index % clos ;

Snip20170301_4.png

有了上面的铺垫,就可以写动态代码了,只需要用户 设置一个列数,知道最终有多少本书,遍历每本书,根据书的索引来计算对应的书的位置即可。

废话不多说了,上代码

// 添加书
- (IBAction)addBook:(id)sender {// 设置列数为 3int clos = 3;// 设置书的宽高分别为 W HCGFloat W = 60;CGFloat H = 70;CGFloat iconH = 50;// 0. 创建书UIView *book = [UIView new];book.backgroundColor = [UIColor redColor];[self.shopView addSubview:book];// 计算书的位置// 获得索引NSUInteger index = [self.books count];// 计算横间距 marginCGFloat margin = (self.shopView.frame.size.width - clos * W) / (clos - 1);// 书 frame 的 XCGFloat x = (index % clos) * (W + margin);// 书 frame 的 YCGFloat y = (index / clos) * (H + margin);book.frame = CGRectMake(x, y, W, H);// 1. 创建书图标UIImageView *bookIcon = [UIImageView new];bookIcon.image = [UIImage imageNamed:@"0"];bookIcon.frame = CGRectMake(0, 0, W, iconH);[book addSubview:bookIcon];// 2.书名UILabel *bookName = [UILabel new];bookName.frame = CGRectMake(0, iconH, W, H-iconH);bookName.text = @"book1";bookName.textAlignment = NSTextAlignmentCenter;[book addSubview:bookName];// 3.添加到数组(分开写好像很难明确如何添加这本书)[self.books addObject:book];}

效果图如下

Snip20170301_5.png

书数据加载方式和对比

到这里购物车里的书已经可以随意添加了,并且可以根据用户的点击,无限制的添加。
如果项目需求修改了,比如变成5列了,宽高什么的也是根据项目自行修改就行。
九宫格布局的代码已经完成了。

现在需要注意的问题是,“书”的属性数据的添加问题,现在简单的来说,书属性有 :书名 + icon。

“书”的各属性值,应该如何赋值呢?



几种简单数据加载方法与对比

  • 直接根据 index 来进行if判断,逐个添加数据
    if (index == 0) {bookIcon.image = [UIImage imageNamed:@"0"];bookName.text = @"Book1";}else if(index == 1){bookIcon.image = [UIImage imageNamed:@"1"];bookName.text = @"Book2";}else if(index == 2){bookIcon.image = [UIImage imageNamed:@"2"];bookName.text = @"Book3";}...
  • 创建一个书的数组,数组存放对应的字典,作为书的属性方法的数据源,从中取值
// 0.创建书的数据源self.books = @[@{@"icon":@"0",@"name":@"book1"},@{@"icon":@"1",@"name":@"book2"},@{@"icon":@"2",@"name":@"book3"},@{@"icon":@"3",@"name":@"book4"},];// 1.书图标
bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];  
// 2.书名
bookName.text = self.books[index][@"name"];
  • 进一步分离数据,将数据信息放到其他文件中,用的时候从文件中读取
    // 获取文件路径NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];// 加载路径中内容放到数组中_books = [NSMutableArray arrayWithContentsOfFile:books];// 1.书图标bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];  // 2.书名bookName.text = self.books[index][@"name"];

对于三种方法简单的比较和点评

  • 第一种直接if判断index的位置

    • 直接把动态判断代码写到代码中,过于死板
    • 代码耦合性太高,及其不利于后期扩展,如果加数据简直是恶魔
    • 一些死布局并且元素个数极少(几个)的时候可以使用
    • 代码重复性高,技术含量低
  • 第二种把数据代码独立出来放到一个数组中

    • 避免了第一种直接写到代码中的低效且恶心的做法
    • 代码还是和数据耦合到一起了,如果后期修改需要修改代码
    • 如果数据量大,及其不利于后期管理和数据的修改
    • 相比第一种有明显的分离,是一种进步
  • 第三种代码和数据分离,放到外部文件中

    • 彻底代码和数据分离,耦合性低,易于扩展
    • 数据放到文件中、后期添加或者修改数据无需改动代码、更加独立
    • 代码更简洁直观,只需关注功能和布局
    • 适合企业开发、这是一种通用方式(网络应用更是如此)

plist文件介绍和使用

简介

  • plist文件全称:Property List文件,中文又叫属性列表文件。
  • 是苹果平台上常用的一种资源描述类文件。
  • 它存储的属性一般都是Xcode里面的基本数据类型或者OC里面的对应类型(NSDictionary,NSArray,NSString等)
  • Xcode会自动进行解析成可以打开和合上的层叠格式
  • 用文本文件打开直接就是XML格式的键值对

plist文件的创建

plist的创建一般直接用Xcode创建,然后在里面添加对应的key和value,(几乎没有人会手写plist文件)

3月-02-2017 15-05-43.gif

plist文件的使用

plist使用就和其他的文件用法一样,先读取目标文件的路径在根据路径加载到数组中。

// 获取文件路径NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];// 加载路径中内容放到数组中_books = [NSMutableArray arrayWithContentsOfFile:books];

plist使用注意

我们自己创建plist文件的时候不能使用info/Info.plist命名

由于每次创建工程的时候,系统会自动生成一个对该项目信息进行描述的info.plist,它会记录项目的一些包名、项目名、开发工具、版本和其他项目的基础信息。

所以我们自己创建plist文件的时候不能使用info/Info.plist

这是为了不和系统的info.plist混淆,实际上自己创建一个info.plist文件和系统重名之后项目是运行不了的。

懒加载(lazyLoad) -- 提升性能

懒加载又叫延迟加载,由于项目运行时候很多数据用不到,可以暂时不创建,等到用的时候在创建,这样节省系统性能。

如以上项目中,就是在添加书的方法中每次创建书籍数组

// 添加书
- (IBAction)addBook:(id)sender {self.books = @[@{@"icon":@"0",@"name":@"book1"},@{@"icon":@"1",@"name":@"book2"},@{@"icon":@"2",@"name":@"book3"},@{@"icon":@"3",@"name":@"book4"},];// 设置列数为 3int clos = 3;// 设置书的宽高分别为 W HCGFloat W = 60;CGFloat H = 70;CGFloat iconH = 50;// 0. 创建书UIView *book = [UIView new];book.backgroundColor = [UIColor redColor];

这样其实非常耗性能,每次都要创建一个数组,然后用过一次就没有用了,对此可以进行一个优化,对于该书籍数据其实就是一个固定数据,每次用一下,用的时候再创建,不用的时候就不管了,这里正好用懒加载最好了。

懒加载实际上就是一个get方法


// 懒加载书籍数据
- (NSMutableArray *)books
{if (_books == nil) {// 获取文件路径NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];// 加载路径中内容放到数组中_books = [NSMutableArray arrayWithContentsOfFile:books];}return _books;
}// 使用的时候,直接用数组就行
bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
bookName.text = self.books[index][@"name"];

MVC 思想的介绍和引入

以上的小Demo,已经告一段落了,可以实现把文件中书籍数据,按九宫格的布局添加到购物车中,也可以一一删除。

思考

还有什么可以改进的吗?
数据就这样存放到数组中就完美了吗?
每次用书的数据的时候,直接从数组总取出来确实方便,但是这样真的足够完美吗?
现在的所有代码几乎都在一个文件中,如果项目大了也这样写吗?
上面把对应的数据加载方式分离到文件中了,其他的业务逻辑能不能也分离一下,能不能让项目的结构更加清晰?

待着这些思考,答案是肯定的,项目的代码还是非常耦合的,并且有个致命的缺点。

  • 每次从数组中取书的信息时候都自己写key值,self.books[index][@"icon"] 如果这个“icon“手误写错,编辑器一点提示也没有
  • 如果书有很多属性,每次手写key值很头痛,并且代码看起来很糟

所以我们需要一种新的方式:因为书是一个对象,可以把它封装成对象,它用有自己的各种属性和方法。这样做有几点好处:

  • 便于管理,在使用的时候直接调用属性的get方法就行。
  • 系统会自动提示get方法,安全性高不会出错,如果写错系统会报错。
  • 代码封装性好,书就是书,而不是每次到数组中去取字典根据对应的key来取值
  • 扩展性好,如果日后需要添加新的属性和方法,直接在”书“类中加就行

MVC介绍

经过上面的分析,可以确定的是书应该独立封装起来保存数据,页面逻辑也应该单独管理,至于ViewController恰好就是两者的桥梁。这样的设计模式就是MVC

  • M : (Model)数据模型,用来存储和保存数据
  • V : (View)UI视图,用来展示给用户看的页面,一些复杂的页面要封装起来放到里面单独管理。
  • C: (Controller)控制器,是两者的桥梁,主要用来处理业务逻辑。

使用MVC模式可以很好的简化项目代码,对不同的模块进行封装,降低耦合性,扩展性也会得到提高,MVC是企业开发常用的设计模式。

MVC的分层封装和使用

经过分析可知,在此小项目中,书和展示的书的UI和ViewController对应MVC的关系:

  • 书 -- Model:用来封装书的各种属性信息和方法
  • 书UI -- View:封装书这个小表象,用于展示给用户看
  • ViewController -- Controller:用来处理整体的业务逻辑,优化代码



废话不多说了,上各层的代码

  • 下面是书的代码封装:存储书的数据和方法

头文件 XYBook.h@interface XYBook : NSObject// 书图标
@property (nonatomic, copy) NSString *icon;
// 书名字
@property (nonatomic, copy) NSString *name;// 对象方法,返回自己对象
- (instancetype)initWithDict:(NSDictionary *)dict;
// 类方法,返回自己对象
+ (instancetype)bookWithDict:(NSDictionary *)dict;
@end#import "XYBook.h"@implementation XYBook- (instancetype)initWithDict:(NSDictionary *)dict
{if (self = [super init]) {[self setValuesForKeysWithDictionary:dict];}return self;
}+ (instancetype)bookWithDict:(NSDictionary *)dict
{return [[self alloc] initWithDict:dict];
}
@end
  • 书的UI的封装:集中布局,减少控制器代码,优化控制器逻辑

XYBookView.h 头文件#import <UIKit/UIKit.h>
@class XYBook;@interface XYBookView : UIView// 只放一个数据属性用来赋值,内部布局,放到.m 中自己管,不暴露给外界
@property (nonatomic, strong) XYBook *book;@end实现文件 .m文件
#import "XYBookView.h"
#include "XYBook.h"@interface XYBookView ()@property (nonatomic, weak) UIImageView *icon;@property (nonatomic, weak) UILabel *label;@end@implementation XYBookView- (instancetype)initWithFrame:(CGRect)frame
{if (self = [super initWithFrame:frame]) {// 1. 创建书图标UIImageView *icon = [UIImageView new];self.icon = icon;[self addSubview:self.icon];// 2.书名UILabel *bookName = [UILabel new];bookName.textAlignment = NSTextAlignmentCenter;self.label = bookName;[self addSubview:self.label];}return self;
}// 重写布局
- (void)layoutSubviews
{[super layoutSubviews];CGSize size = self.frame.size;self.icon.frame = CGRectMake(0, 0, size.width , size.height * 0.7);self.label.frame = CGRectMake(0, size.height * 0.7, size.width, size.height *(1 - 0.7));}// 设置书的属性
- (void)setBook:(XYBook *)book
{_book = book;self.icon.image = [UIImage imageNamed:book.icon];self.label.text = book.name;
}
@end
  • 控制器:不管细节,专注处理逻辑

// 懒加载数据源
- (NSMutableArray *)books
{if (_books == nil) {_books = [NSMutableArray array];// 获取文件路径NSString *books = [[NSBundle mainBundle] pathForResource:@"bookdata" ofType:@"plist"];// 加载路径中内容放到数组中NSMutableArray *arrayM = [NSMutableArray arrayWithContentsOfFile:books];for (NSDictionary *dict in arrayM) {XYBook *book = [XYBook bookWithDict:dict];[_books addObject:book];}}return _books;
}// 添加书
- (IBAction)addBook:(id)sender {// 设置列数为 3int clos = 3;// 设置书的宽高分别为 W HCGFloat W = 60;CGFloat H = 70;// 0. 创建书XYBookView *bookView = [XYBookView new];bookView.backgroundColor = [UIColor redColor];// 计算书的位置// 获得索引NSUInteger index = [self.shopView.subviews count];// 计算横间距 marginCGFloat margin = (self.shopView.frame.size.width - clos * W) / (clos - 1);// 书 frame 的 XCGFloat x = (index % clos) * (W + margin);// 书 frame 的 YCGFloat y = (index / clos) * (H + margin);bookView.frame = CGRectMake(x, y, W, H);[self.shopView addSubview:bookView];// 给书的UI设置数据bookView.book = self.books[index];[self checkState];self.removeBtn.enabled = YES;
}// 移除书
- (IBAction)removeBook:(id)sender {[[self.shopView.subviews lastObject] removeFromSuperview];[self checkState];self.addBtn.enabled = YES;
}

以上就是对于本Demo的最终MVC封装版
不同部分各司其职,负责自己的模块
项目的健壮性和封装性也也到了对应的提高



小记

一个简单的九宫格购物车的小Demo,真是麻雀虽小五脏俱全。

关于这个项目的完整代码,欢迎私聊或评论找我要,如果文章有任何问题或有其他技术问题,欢迎随时和我交流。

最后放一张项目效果图
2017-03-02 17.47.23.gif

 

转载于:https://www.cnblogs.com/xiaoyouPrince/p/6494710.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【GlobalMapper精品教程】007:如何加载谷歌卫星影像?

“Global Mapper支持所有OGC标准数据源类型,例如用于流式栅格地图的WMS / WMTS,用于矢量数据集的WFS和用于为指定区域下载单个数据文件的WCS。预先切片的图像和地形数据集也可以使用OSM(OpenStreetMaps)、TMS(Tiled Map Service)和Google Maps瓦片架构支持。您只需要选择适当…

Mysql清空表(truncate)与删除表中数据(delete)的区别

2019独角兽企业重金招聘Python工程师标准>>> 为某基于wordpress搭建的博客长久未除草&#xff0c;某天升级的时候发现已经被插入了几万条垃圾留言&#xff0c;如果一条条删除那可真是累人的活。遂考虑直接进入mysql直接清空表或者删除表中数据。 本文记录一下这2种操…

[转]云原生到底是什么?

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#x1f61c; &#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525; &#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4ac;格言&#xf…

膛目结舌的代码技巧!一看就是冷暴力~~~~

你见过哪些令你膛目结舌的代码技巧&#xff1f; 代码世界有很多令人大呼小叫的技巧&#xff01;有的代码像魔术师一样巧妙地隐藏了自己&#xff0c;有的像魔法师一样让你眼花缭乱&#xff0c;还有的像瑜伽大师一样灵活自如。它们让我们惊叹不已&#xff0c;让我们觉得自己仿佛置…

联合线程

联合线程实际上就是把多线程又联合成了一个线程&#xff0c;但这里还是要比单线程灵活很多&#xff0c;比如说&#xff0c;我可以让一个线程到运行到某一个条件再联合其他线程。当前线程与其他线程联合在一起&#xff0c;又一种让出cpu&#xff0c;而且直到别个线程运行完&…

Kafka学习征途:不再依赖ZK的KRaft

【Kafka】| 总结/Edison Zhou1新的KRaft架构在Kafka 2.8之前&#xff0c;Kafka重度依赖于Zookeeper集群做元数据管理和集群的高可用&#xff08;即所谓的共识服务&#xff09;。在Kafka 2.8之后&#xff0c;引入了基于Raft协议的KRaft模式&#xff0c;支持取消对Zookeeper的依赖…

探索java世界中的日志奥秘

java日志简单介绍 对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪&#xff0c;基于日志的业务逻辑统计分析等都离不日志。JAVA领域存在多种日志框架&#xff0c;目前常用的日志框架包括Log4j&#xff0c;Log4j 2&#xff0c;Commons Logging&#xff0c;Slf4j&…

RabbitMQ细说之开篇

前言关于消息中间件的应用场景&#xff0c;小伙伴们应该都耳熟能详了吧&#xff0c;比如经常提到的削峰填谷、分布式事务、异步业务处理、大数据分析等等&#xff0c;分布式消息队列成为其中比较关键的桥梁&#xff0c;也就意味着小伙伴们得掌握相关技能&#xff1b;当下相对比…

【Java】五种常见排序之-----------冒泡排序

冒泡排序&#xff1a; 原理: 将关键字较小的值不断地上浮&#xff0c;将关键字值较大的不断下沉&#xff1b;时间复杂度&#xff1a;O(n^2)空间复杂度&#xff1a;最优&#xff08;即已经排好序&#xff09;为0&#xff0c;平均空间复杂度为O(1);核心代码&#xff1a;for(int i…

混战的低代码江湖,如何区分「李逵」和「李鬼」?

作者&#xff1a;APICloud 创始人刘鑫 这两年&#xff0c;无论是资本层面&#xff0c;还是企业IT部门的关注&#xff0c;“低代码”都是绝对的热点。互联网圈也似在一夜之间冒出了各种各样的低代码公司。 到底什么是低代码&#xff1f;低代码是新技术么&#xff1f;低代码开发能…

关于捕获键盘信息的processDialogkey方法2--具体应用

自定义控件里的keydown方法无法捕获所有的按键消息的处理方法1&#xff08;自定义控件里的keydown方法无法获取的键值如上下左右键等&#xff09; 处理办法具体如下&#xff1a; 1、首先在自定义控件UserControl1中重写ProcessDialogKey方法 自定义控件UserControl1中重写Proce…

指针

指针 题目一&#xff1a; 计算两数的和与差 本题要求实现一个计算输入的两数的和与差的简单函数。 函数接口定义&#xff1a; void sum_diff( float op1, float op2, float psum, float pdiff ); 其中op1和op2是输入的两个实数&#xff0c;psum和pdiff是计算得出的和与差。 裁判…

【MapGIS精品教程】006:MapGIS根据经纬度计算各比例尺图幅编号

己知某点的经纬度或图幅西南图廓点的经纬度,计算该点所在图幅号。 例题一:某点的经度为11433′45″,纬度为3922′30″,计算所在1:250000图幅的编号。 文章目录 1. 公式计算编号的方法2. 软件计算编号的方法1. 公式计算编号的方法 求解过程: 第一步,利用下列公式计算其所…

分析完百年飞机空难数据,我发现了这几条“保命”小秘诀

来 源&#xff5c;Giao数据 数 据 | YaJie 文 章 | 张子豪,YaJie 本文爬取了飞机失事网1908-2020年空难相关数据&#xff0c;包括空难发生次数、机组和乘客的死亡人数与死亡率、不同季节的空难发生次数、空难相关文本的关键词、空难高发地、空难高发航空公司、空难高发机型以…

用 QuestPDF操作生成PDF更快更高效!

QuestPDFQuestPDF是一个开源的工具库&#xff0c;可以在.NET或者.Net Core中生成pdf文档它提供了一个布局引擎&#xff0c;设计时考虑到了完整的分页支持以及灵活性要求&#xff01;比市面上常见的Aspose和iTextSharp好用太多了&#xff01;GitHub地址安装Install-Package Ques…

ASP.NET vs MVC vs WebForms

许多ASP.NET开发人员开始接触MVC认为MVC与ASP.NET完全没有关系&#xff0c;是一个全新的Web开发&#xff0c;事实上ASP.NET是创建WEB应用的框架而MVC是能够用更好的方法来组织并管理代码的一种更高级架构体系&#xff0c;所以可以称之为ASP.NET MVC。 我们可将原来的ASP.NET称为…

在Blazor 中自定义权限验证

Blazor是什么Blazor 是微软在 .NET 里推出的一个 WEB 客户端 UI 交互的框架&#xff0c;使用 Blazor 你可以代替 JavaScript 来实现自己的页面交互逻辑&#xff0c;可以很大程度上进行 C# 代码的复用&#xff0c;Blazor 对于 .NET 开发人员来说是一个不错的选择。需求背景其实我…

【ArcGIS微课1000例】0035:地图面状符号设计教程

地图符号是表示地图内容的基本手段,它由形状不同、大小不一、色彩有别的图形和文字组成。 地图符号是地图的语言,是一种图形语言。它与文字语言相比较,最大的特点是形象直观,一目了然。 本文讲解ArcGIS中面状符号设计方法。 文章目录 一、新建符号样式二、面状符号设计1. 斜…

MySQL夺命15问,你能坚持到第几问?

前言 MySQL在面试中经常被问到&#xff0c;本文总结了面试中的经典问题。 1. 数据库三大范式是什么&#xff1f; 第一范式&#xff1a;每个列都不可以再拆分。 第二范式&#xff1a;在第一范式的基础上&#xff0c;非主键列完全依赖于主键&#xff0c;而不能是依赖于主键的一部…

ios元素定位

原文地址http://www.cnblogs.com/meitian/p/7373460.html 第一种&#xff1a;通过Appium1.6的Inspector来查看 具体安装方式前面的随笔已经介绍了&#xff1a;http://www.cnblogs.com/meitian/p/7360017.html可以通过定位找到元素xpath或name个人不推荐用这个方法&#xff0c;实…