本节内容
- 私有变量的使用,实现快速调整边框宽度。
- 支持同时显示一张至六张图片,自动调整图片大小
- 在Cell为大图片框,CellRow为大图片框中的每一个小图片。
- ForEach的用法
知识点
- ForEach 使用images数组本身的下标
ForEach(images,id:\.self){image in
- 获取屏幕的宽度
UIScreen.main.bounds.width
实战代码
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)!)
}
PostCell.swift
//
// PostSell.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/29.
//
import SwiftUI
struct PostCell: 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 {
PostImageCell(images: post.images, width: UIScreen.main.bounds.width-30)
}
// 一个细分割线
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 {
PostCell(post: postList.list[1])
// PostSell(post: Post(avata: "d0c21786ly1gavj2c0kcej20c8096dh7.jpg", vip: true, name: "用户昵称", date: "2020-1-1-1", isFollowed: false))
}
}
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("点赞")
})
}
}
PostDetailView.swift
//
// PostDetailView.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/30.
//
import SwiftUI
struct PostDetailView: View {
let post:Post
var body: some View {
List{
PostCell(post: post)
.listRowInsets(EdgeInsets())
ForEach(1...10,id:\.self){i in
Text("评论\(i)")
}
}
// 下方两个语句避免跳转后的导航栏显示空白
.navigationTitle("详情")
// 只显示小标题
.navigationBarTitleDisplayMode(.inline)
}
}
struct PostDetailView_Previews: PreviewProvider {
static var previews: some View {
PostDetailView(post: postList.list[0])
}
}
PostImageCell.swift
//
// ContentView.swift
// WeiboDemo
//
// Created by EchoSun on 2020/11/28.
//
import SwiftUI
// 以k开头是常量的习惯命名法
// 设置为 private 则仅暴露给当前文件
private let kImageSpace: CGFloat = 6
struct PostImageCell: View {
let images : [String]
let width : CGFloat
var body : some View{
Group {
if images.count == 1 {
PostImageCellRow(images: images, width: width)
// loadImage(name: images[0])
// .resizable()
// .scaledToFill()
// // 按照 4:3 的宽高比裁切图像。
// .frame(width: width, height: 0.75*width)
// .clipped()
}
else if images.count==2{
PostImageCellRow(images: images, width: width)
}
else if images.count==3{
PostImageCellRow(images: images, width: width)
}
else if images.count==4{
VStack(spacing:kImageSpace){
PostImageCellRow(images: Array(images[0...1]), width: width)
PostImageCellRow(images: Array(images[2...3]), width: width)
}
}
else if images.count==5{
VStack(spacing:kImageSpace){
PostImageCellRow(images: Array(images[0...1]), width: width)
PostImageCellRow(images: Array(images[2...4]), width: width)
}
}
else if images.count==6{
VStack(spacing:kImageSpace){
PostImageCellRow(images: Array(images[0...2]), width: width)
PostImageCellRow(images: Array(images[3...5]), width: width)
}
}
}
}
}
struct PostImageCellRow: View {
let images : [String]
let width : CGFloat
var body: some View{
HStack(spacing:kImageSpace){
ForEach(images,id:\.self){image in
loadImage(name: image)
.resizable()
.scaledToFill()
.frame(width: (self.width-kImageSpace*CGFloat(self.images.count-1))/CGFloat(self.images.count), height: (self.width-kImageSpace*CGFloat(self.images.count-1))/CGFloat(self.images.count))
.clipped()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let images = postList.list[0].images
let width = UIScreen.main.bounds.width
return Group{
PostImageCell(images: Array(images[0...0]),width: width)
PostImageCell(images: Array(images[0...1]),width: width)
PostImageCell(images: Array(images[0...2]),width: width)
PostImageCell(images: Array(images[0...3]),width: width)
PostImageCell(images: Array(images[0...4]),width: width)
PostImageCell(images: Array(images[0...5]),width: width)
}
.previewLayout(.fixed(width: width, height: 500))
}
}
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
// 默认样式会有箭头,不显示的话需要加一个ZStack
// ZStack 垂直于屏幕方向排列
ZStack{
PostCell(post:post)
NavigationLink(
destination: PostDetailView(post: post),
label: {
EmptyView()
})
.hidden()
}
.listRowInsets(EdgeInsets())
}
}
}
}
struct PostListView_Previews: PreviewProvider {
static var previews: some View {
// 导航栏
NavigationView{
PostListView()
// 设置导航栏标题
.navigationTitle("title")
// 隐藏导航栏,如果使用的话必须要设置其标题
// .navigationBarHidden(true)
}
}
}
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)
}
}