Swift版PhotoStackView——照片叠放视图

前言

之前流行过一种图片展示视图——photo stack,即照片叠放视图。大致上是这个样子的:
示例
(图片出自code4app)
现在我们已经能够使用UICollectionViewLayout来实现这种视图了。Apple给的示例代码中就有这样一个layout,并且示例代码中不仅仅是展示这样的视图,还有非常棒的layout过度动画(结合手势)。在这之前,也有非常多的开源代码能实现这样的效果。本文正是借鉴了开源的源代码“PhotoStackView”,使用Objective-C实现,并且带手势移动图片的功能。由于这是上一学期课设的时候拿来用的库,结果现在找不到了,无法给出链接,还望见谅。

最后的效果图如下

  • 移动、添加与删除
    stackView1

  • 下一张
    stackView2

这样重复造轮子的目的是什么呢?一方面,不使用UICollectionViewLayout而是纯粹的用UIView来实现,提高灵活性,方便“私人订制”。另一方面,学习大神的源代码,从中学习一下自定义库的书写方式等。最后,swift。。天杀的swift,是谁说swift对新手友好来着表情
当然,这里也不是简单的对源代码的搬运、抄袭与翻译,我还根据自己的需要给他改了一个bug,添加了一些功能,比如全屏展示与返回等。示例图如下:

  • 全屏展示
    stackView3

(图片有些卡,测试运行时还是非常流畅的)

思路

  • 使用若干和自己一样大的view来装载图片,每次添加新图片到视图上时为其随机旋转一定角度,把最前面的这张旋转角度设为0。
  • “view装载图片”的意思是,view里面放一个imageView,为什么不直接用imageVIew呢?因为我们要放边框,边框可以是图片,另外在highlighted状态下也可以用图片或自定义颜色铺在图片上,因此使用一个view统一管理
  • 添加pan手势,当手指移动时让view跟着做相应平移,手指松开时根据velocity决定返回还是移到最后
  • 综上,我们还需要不少辅助方法,例如获取顶部图片和其下标、使用动画移动view、让view旋转到某一度数等

代码

computed property & stored property

oc中可以声明属性然后覆写setter或getter,从而实现赋值或取值时进行一些操作的功能,如下代码

@property(strong, nonatomic) NSString *someString

- (void)setSomeString() {
  ...}
- (NSString *)someString() {
  ...}

swift中相对应的写法,目前我知道的有两种,一种是使用computed property 的set和get,缺点是必须同时声明一个stored property(?可以不用吗,求科普),很像oc2.0之前的属性。另一种是使用监听器(didSet和willSet),缺点是只能对setter操作,不能对getter操作。
但是,要想实现重写父类的setSomeThing这样的功能,只能通过监听器的方法。否则会报错
Xcode报错

由上,该类用到的属性如下(部分):

    //MARK: computed property

    var s_rotationOffset: CGFloat = 0.0
    /// the scope of offset of rotation on every photo except the first one. default is 4.0.
    /// ie, 4.0 means rotate iamge with degree between (-4.0, 4.0)
    var rotationOffset: CGFloat {
        set {
            if s_rotationOffset == newValue {
                return
            }

            s_rotationOffset = newValue
            reloadData()
        }
        get {
            return s_rotationOffset
        }
    }

    var s_photoImages: [UIView]?
    var photoImages: [UIView]? {
        set {
            //remove all subview and prepare to re-add all images from data source
            for view in subviews {
                view.removeFromSuperview()
            }

            if let images = newValue {
                for view in images {
                    //keep the original transfrom for the existing images
                    if let index = find(images, view), count = s_photoImages?.count where index < count {
                        let existingView = s_photoImages![index]
                        view.transform = existingView.transform
                    } else {
                        makeCrooked(view, animated: false)
                    }

                    insertSubview(view, atIndex: 0)
                }
            }

            s_photoImages = newValue
        }
        get {
            return s_photoImages
        }
    }

    override var highlighted: Bool {
        didSet {
            let photo = self.topPhoto()?.subviews.last as! UIImageView
            if highlighted {
                let view = UIView(frame: self.bounds)
                view.backgroundColor = self.highlightColor
                photo.addSubview(view)
                photo.bringSubviewToFront(view)
            } else {
                photo.subviews.last?.removeFromSuperview()
            }
        }
    }

    override var frame: CGRect {
        didSet {
            if CGRectEqualToRect(oldValue, self.frame) {
                return
            }

            reloadData()
        }
    }

上面大部分还有其他省略的大都是一样的思路:设置新值时调用reloadData刷新,主要是上面那个photoImage数组的setter:首先移除当前所有的子视图,接下来遍历新数组,那句判断if let index = find(images, view), count = s_photoImages?.count where index < count的作用是判断此次循环中view是否是之前已经添加到界面上的,如果是,则保留其transform不变,否则为其重新生产一个旋转角度(产生照片堆效果)&#

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值