iOS开发实战-第5节-微博内容中同时显示多张照片

本节内容

  1. 私有变量的使用,实现快速调整边框宽度。
  2. 支持同时显示一张至六张图片,自动调整图片大小
  3. 在Cell为大图片框,CellRow为大图片框中的每一个小图片。
  4. ForEach的用法

知识点

  1. ForEach 使用images数组本身的下标
ForEach(images,id:\.self){image in
  1. 获取屏幕的宽度
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)
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值