iOS开发实战-第4节-进一步完善微博页面

本节内容聚焦于iOS微博页面的优化,包括增加图片、抽象创建图片的方法、按4:3比例裁剪、添加分割线、组织数据结构、自定义评论和点赞按钮样式、调整TableView与TableViewCell样式,以及修改按钮响应范围。

本节内容

为上一节创建的微博页面增加插图

知识点

  1. 为重复的方法新建一个函数,抽象出创建图片的方法。
  2. 按照 4:3 的宽高比切分图片。
  3. 使用 Divider() 绘制一个细分割线
  4. Post 结构体中仅保存于数据有关的内容,而将View相关的只读属性存放在extension中。
  5. 添加评论和点赞按钮。
  6. 修改TableView默认样式,不显示默认的线
  7. 修改TableViewCell默认样式,点击后不再显示灰色底色
  8. 修改按钮的默认样式,限制按钮的响应范围,只有单击按钮区域才会有动作

实战代码

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)!)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值