本节内容
为上一节创建的微博页面增加插图
知识点
- 为重复的方法新建一个函数,抽象出创建图片的方法。
- 按照 4:3 的宽高比切分图片。
- 使用 Divider() 绘制一个细分割线
- Post 结构体中仅保存于数据有关的内容,而将View相关的只读属性存放在extension中。
- 添加评论和点赞按钮。
- 修改TableView默认样式,不显示默认的线
- 修改TableViewCell默认样式,点击后不再显示灰色底色
- 修改按钮的默认样式,限制按钮的响应范围,只有单击按钮区域才会有动作
实战代码
PostSell.swift
//
// PostSell.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/29.
//
import SwiftUI
struct PostSell: View {
let post:Post
var body: some View {
VStack(alignment: .leading, spacing: 10){
HStack(spacing: 5.0) {
// 使用 library 窗口快速添加 ⇧⌘ L
// Image(uiImage: UIImage(named: post.avatar)!)
post.avatarImage
.resizable() // 调整到合适的大小
.scaledToFit() // 按比例缩放
// 下面两个语句顺序很关键,由于图片是矩形的,若这两句顺序颠倒,则最终效果是"直边椭圆形"。
.clipShape(Circle()) // 切成一个圆形
.frame(width:50, height: 50) // 设置大小
//
.overlay(
// 为了进一步简化代码,方便我们快速设置角标,我们将头像右下角的”V“抽象到一个新的视图中。
PostVIPBadge(vip:post.vip)
// 设置一个偏移量
.offset(x: 16, y: 16)
)
// VStack:纵向排列
// leading: 左对齐
// spacing: 四种间隔
VStack(alignment: .leading, spacing: 5.0) {
Text(post.name) // 限制行数
.font(Font.system(size: 16))
.foregroundColor(.red)
.lineLimit(1)
Text(post.date)
.font(Font.system(size: 11))
.foregroundColor(.gray)
}
.padding(.leading,10)
Spacer() // 中间填充空间
if !post.isFollowed{
Button(action: {
print("Click follow button")
}) {
Text("关注")
.font(.system(size: 14))
.foregroundColor(.orange)
.frame(width: 50, height: 26) // 设置frame可以便于画矩形边框,也可以增大按键区域。
.overlay(
RoundedRectangle(cornerRadius: 13)
.stroke(Color.orange,lineWidth: 1) // 绘制轮廓
)
}
// 限制按钮的响应范围,只有单击按钮区域才会有动作
.buttonStyle(BorderlessButtonStyle())
}
}
// 显示微博内容
Text(post.text)
.font(.system(size: 17))
if !post.images.isEmpty{
loadImage(name: post.images[0])
.resizable()
.scaledToFill()
// 按照 4:3 的宽高比裁切图像。
// UIScreen.main.bounds.width 获取屏幕的宽度,然后减去左右两边各15的padding
.frame(width: UIScreen.main.bounds.width-30, height: 0.75*(UIScreen.main.bounds.width-30))
.clipped()
}
// 一个细分割线
Divider()
// 评论按钮和点赞按钮
HStack(spacing:0){
Spacer()
PostCellToolbarButton(image: "message", text: post.commentCountText, color: .black, action: {
print("Click comment button")
})
Spacer()
PostCellToolbarButton(image: "heart", text: post.likeCountText, color: .black, action: {
print("Click like button")
})
Spacer()
}
Rectangle()
.padding(.horizontal,-15)
.frame(height:10)
.foregroundColor(Color(red: 238/255, green: 238/255, blue: 238/255))
}
.padding(.horizontal,15)
.padding(.top,15)
}
}
struct PostSell_Previews: PreviewProvider {
static var previews: some View {
PostSell(post: postList.list[1])
// PostSell(post: Post(avata: "d0c21786ly1gavj2c0kcej20c8096dh7.jpg", vip: true, name: "用户昵称", date: "2020-1-1-1", isFollowed: false))
}
}
PostListView.swift
//
// PostListView.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/30.
//
import SwiftUI
struct PostListView: View {
// 构造方法
init() {
// TableView 不显示默认的线
UITableView.appearance().separatorStyle = .none
// 点击后不再显示灰色底色
UITableViewCell.appearance().selectionStyle = .none
}
var body: some View {
List{
ForEach(postList.list){ post in
PostSell(post:post)
.listRowInsets(EdgeInsets())
}
}
}
}
struct PostListView_Previews: PreviewProvider {
static var previews: some View {
PostListView()
}
}
PostCellToolbarButton.swift
//
// PostCellToolbarButton.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/30.
//
import SwiftUI
struct PostCellToolbarButton: View {
let image:String
let text :String
let color: Color
// 使用swift中的闭包定义一个返回值为空的函数
let action:()->Void
var body: some View {
Button(action:action) {
HStack(spacing:5){
Image(systemName: image)
.resizable()
// 调整到适应,不会有显示不全的情况
.scaledToFit()
.frame(width: 18, height: 18)
Text(text)
.font(.system(size: 15 ))
}
}
.foregroundColor(color)
// 限制按钮的响应范围,只有单击按钮区域才会有动作
.buttonStyle(BorderlessButtonStyle())
}
}
struct PostCellToolbarButton_Previews: PreviewProvider {
static var previews: some View {
PostCellToolbarButton(image: "heart", text: "点赞", color: .red, action: {
print("点赞")
})
}
}
PostVIPBadge.swift
//
// PostVIPBadge.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/29.
//
import SwiftUI
struct PostVIPBadge: View {
let vip:Bool
var body: some View {
Group {
if vip{
Text("V")
.bold()
.font(Font.system(size: 11))
.frame(width: 15, height: 15)
.foregroundColor(.yellow)
.background(Color.red)
.clipShape(Circle())
.overlay(
RoundedRectangle(cornerRadius: 7.5)
.stroke(Color.white,lineWidth: 1)
)
}
}
}
}
struct PostVIPBadge_Previews: PreviewProvider {
static var previews: some View {
PostVIPBadge(vip: true)
}
}
Post.swift
//
// Post.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/29.
//
import SwiftUI
// Codable 是一种可以编码和解码的数据类型
// 结构体与json的名称和类型必须对应,否则解析出错
struct PostList:Codable {
var list:[Post]
}
// data model 看不到的数据模型,与View无关
struct Post:Codable,Identifiable{
// 下方的所有变量又叫做实例变量,仅在Post的实例创建时创建。
// let 声明常量; var 声明变量. 不清楚的话可以先声明成常量,等到需要变动时再改成变量。
let id:Int // 微博ID
let avatar:String // 用户头像,图片名称
let vip:Bool // 是否是VIP
let name:String // 用户名
let date:String // 日期
var isFollowed:Bool // 是否关注
let text:String // 微博内容
let images:[String] // 微博图片
var commentCount:Int // 评论数
var likeCount:Int // 点赞数
var isLiked:Bool // 是否点赞
}
// Post 的扩展,即和View相关的内容
extension Post{
// 返回头像对象
var avatarImage:Image{
return loadImage(name: avatar)
}
// 只读属性,又叫计算属性(Calculated property),只能获取值,不能赋值
var commentCountText:String{
if commentCount <= 0{
return "评论"
}
if commentCount<=1000{
return "\(commentCount)"
}
return String(format: "%.1fK", Double(commentCount)/1000)
}
var likeCountText:String{
if likeCount <= 0{
return "点赞"
}
if likeCount<=1000{
return "\(likeCount)"
}
return String(format: "%.1fK", Double(likeCount)/1000)
}
}
// 全局变量,任何地方都可以调用
let postList:PostList = loadPostListData(fileName:"PostListData_recommend_1.json")
func loadPostListData(fileName:String)->PostList{
guard let url = Bundle.main.url(forResource: fileName, withExtension: nil) else{
fatalError("Can not find \(fileName) in main Bundle")
}
guard let data = try? Data(contentsOf: url)else{
fatalError("Can not load \(url)")
}
// // 三种方法处理异常
// // 方法1 推荐 try? 如果解析成功就有值,否则为 nil
// let list1 = try?JSONDecoder().decode(PostList.self, from: data)
// print(list1 ?? "None")
// // 方法2 使用try 处理异常,能够接收到错误,并且输出。但是,语法结构复杂。
// do {
// let list2 = try JSONDecoder().decode(PostList.self, from: data)
// print(list2);
// } catch {
// print(error);
// }
// // 方法3 不推荐 try! 如果解析成功就有值,否则崩溃
// let list3 = try!JSONDecoder().decode(PostList.self, from: data)
// print(list3)
guard let list = try?JSONDecoder().decode(PostList.self, from: data) else{
fatalError("Can not parse post list json data")
}
return list
}
// 载入图片,并以Image对象的形式返回
func loadImage(name:String)->Image{
return Image(uiImage: UIImage(named: name)!)
}
本节内容聚焦于iOS微博页面的优化,包括增加图片、抽象创建图片的方法、按4:3比例裁剪、添加分割线、组织数据结构、自定义评论和点赞按钮样式、调整TableView与TableViewCell样式,以及修改按钮响应范围。

1167

被折叠的 条评论
为什么被折叠?



