iOS/Swift 头像轮播组件

iOS/Swift 头像轮播组件

在 iOS 开发中,使用轮播图展示头像是一种常见的 UI 设计方式。主要用于展示一个可循环滚动的头像轮播视图,并带尾随指示的小灰点。

主要功能

  • 通过 UIImageView 依次排列头像,自动向左滚动,最左边变小消失,最右边变大出现
  • 通过 UIView.animate 实现平滑滚动动画

代码解析

1. 初始化头像数据

swift 复制代码
private lazy var avatarImages: [UIImage] = {
    let imageNames = ["people-1", "people-2", "people-3", "people-4", "people-5", "people"]
    return imageNames.compactMap { UIImage(named: $0) }
}()

这里 lazy var 避免了在 init 方法中立即解包,提高了效率。compactMap 过滤掉可能为空的 UIImage,保证数据完整性。

2. 初始化小灰点

swift 复制代码
private lazy var dotViews: [UIView] = (0..<4).map { _ in
    let dotView = UIView()
    dotView.backgroundColor = UIColor(hex: 0xEAEEF1)
    dotView.layer.cornerRadius = 5
    return dotView
}

创建了 4 个圆形小灰点,用来滚动。

3. 头像滚动逻辑

通过 UIView.animate 实现头像滚动效果。

swift 复制代码
UIView.animate(withDuration: 0.3, animations: {
    self.centerAvatar.forEach { $0.frame.origin.x -= self.avatarWidth }
}) { _ in
    self.updateAvatarOrder()
}

这里 forEach 遍历所有头像,将其向左移动 avatarWidth 的距离。动画完成后,调用 updateAvatarOrder() 重新调整视图顺序。

最后附上完整实现代码

swift 复制代码
// 头像图片数组 (使用 lazy var 避免立即解包)
private lazy var avatarImages: [UIImage] = {
    let imageNames = ["people-1", "people-2", "people-3", "people-4", "people-5", "people"]
    return imageNames.compactMap { UIImage(named: $0) }
}()

// 小灰点数组 (使用 map 简化初始化)
private lazy var dotViews: [UIView] = (0..<4).map { _ in
    let dotView = UIView()
    dotView.backgroundColor = UIColor(hex: 0xEAEEF1)
    dotView.layer.cornerRadius = 5
    return dotView
}

private var firstAvatar: UIImageView!
private var lastAvatar: UIImageView!
private var newAvatarView: UIImageView!
private var centerAvatar: [UIImageView] = []
private var index = 3

private func setup() {
    backgroundColor = .clear
    layer.masksToBounds = true
    setupAvatars()
    setupDots()
}

private func setupAvatars() {
    // 添加头像视图
    for (index, image) in avatarImages.enumerated() {
        if index < 4 {
            let avatarView = UIImageView()
            avatarView.image = image
            avatarView.contentMode = .scaleAspectFill
            avatarView.frame = CGRect(x: CGFloat(index) * 60 - CGFloat(index) * 10, y: 0, width: 60, height: 60)
            avatarView.addBorder(width: 2,borderColor: .white)
            avatarView.layerCornerRadius = avatarView.width/2
            addSubview(avatarView)
            if index == 3 {
                lastAvatar  = avatarView
            }
            if index == 0 {
                firstAvatar  = avatarView
            } else {
                centerAvatar.append(avatarView)
            }
        }
    }
}

private func setupDots() {
    let dotSpacing: CGFloat = 5
    let dotSize: CGFloat = 10
    let dotY = (frame.height - dotSize)/2
    for (index, dotView) in dotViews.enumerated() {
        dotView.frame = CGRect(x: lastAvatar.right + CGFloat(index) * (dotSize + dotSpacing) + 5, y: dotY, width: dotSize, height: dotSize)
        addSubview(dotView)
    }
}

func createNewAvatarView() {
    index += 1
    // 添加右侧最左边点位置添加新头像
    self.newAvatarView = MyCopyImageView()
    self.newAvatarView.frame = self.dotViews[0].frame
    self.newAvatarView.contentMode = .scaleAspectFill
    self.newAvatarView.addBorder(width: 2,borderColor: .white)
    self.newAvatarView.layerCornerRadius = self.newAvatarView.width/2
    self.newAvatarView.alpha = 0.1
    self.newAvatarView.image = avatarImages[index%avatarImages.count]
    self.addSubview(self.newAvatarView)
}

func startAnimation() {
    createNewAvatarView()
    UIView.animate(withDuration: 0.4, delay: 0, options: [.curveLinear], animations: {

        // 最左边头像变小消失
        self.firstAvatar.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

        // 移动头像和小灰点
        for avatarView in self.centerAvatar {
            avatarView.frame.origin.x -= 50
        }
        for dotView in self.dotViews {
            dotView.frame.origin.x -= 15
        }

//            // 右侧最左边点位置添加新头像并变大出现
        self.newAvatarView.frame = .init(x: self.lastAvatar.x+50, y: 0, width: 60, height: 60)
        self.newAvatarView.layerCornerRadius = self.newAvatarView.width/2
        self.newAvatarView.alpha = 1

    }, completion: { _ in
        // 动画完成后, 替换最左边头像
        self.firstAvatar.removeFromSuperview()
        self.firstAvatar = self.centerAvatar.first
        self.centerAvatar.removeFirst()
        self.lastAvatar = self.newAvatarView
        self.centerAvatar.append(self.lastAvatar)
        // 恢复小灰点位置
        for dotView in self.dotViews {
            dotView.frame.origin.x += 15
        }
        // 递归调用继续动画
        Timer.after(0.6) {
            self.startAnimation()
        }
    })
}
相关推荐
大熊猫侯佩4 分钟前
Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”
ios·swift·编程语言
黑科技iOS上架1 小时前
没有mac电脑如何借助windows系统上传ipa到App Store
经验分享·ios
Layer2 小时前
从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
ios·aigc
Cutecat_11 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
大熊猫侯佩16 小时前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift
游戏开发爱好者817 小时前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
大熊猫侯佩1 天前
WWDC26 最被忽视的王炸:告别“伪并发”陷阱,Swift 6.4 的 async defer
ios·swift·编程语言
h-189-53-6712071 天前
苹果开发者账号防关联3.2f隔离环境传包提审iOS开发上架的高效隔离方案:iOSUploader工具实用解析
ios·ios上架·ios审核·苹果审核·苹果开发者账号·苹果开发者封号
Legendary_0081 天前
LDR6020P:iPad 一体式皮套键盘 OTG 应用的核心引擎
ios·计算机外设·ipad
Digitally2 天前
如何高效地将文件从电脑传输到 iPad:6 种简单方法
ios·电脑·ipad