/// 照片查看视图控制器 class PhotoBrowserViewController: UIViewController { /// 图像 URL 数组 var urls: [NSURL] /// 选中照片索引 var selectedIndexPath: NSIndexPath // MARK: - 构造函数 init(urls: [NSURL], indexPath: NSIndexPath) { self.urls = urls self.selectedIndexPath = indexPath super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
- 在
HomeTableViewController
加载照片查看视图控制器,并且传递数据
// 注册通知
NSNotificationCenter.defaultCenter().addObserverForName(WBStatusSelectedPhotoNotification,
object: nil,
queue: nil) { [weak self] (n) -> Void in
guard let urls = n.userInfo?[WBStatusSelectedPhotoURLKey] as? [NSURL] else {
return
}
guard let indexPath = n.userInfo?[WBStatusSelectedPhotoIndexPathKey] as? NSIndexPath else {
return
}
let vc = PhotoBrowserViewController(urls: urls, indexPath: indexPath)
self?.presentViewController(vc, animated: true, completion: nil)
}
搭建界面
- 在
UIButton+Extension
中扩展便利构造函数
/// 便利构造函数
///
/// - parameter title: title
/// - parameter color: color
/// - parameter fontSize: 字体大小
/// - parameter imageName: 图像名称
/// - parameter backColor: 背景颜色
///
/// - returns: UIButton
convenience init(title: String, fontSize: CGFloat, color: UIColor, imageName: String?, backColor: UIColor? = nil) {
self.init()
setTitle(title, forState: .Normal)
setTitleColor(color, forState: .Normal)
if let imageName = imageName {
setImage(UIImage(named: imageName), forState: .Normal)
}
titleLabel?.font = UIFont.systemFontOfSize(fontSize)
backgroundColor = backColor
sizeToFit()
}
- 懒加载控件
// MARK: - 懒加载控件
/// collectionView
private var collectionView: UICollectionView = UICollectionView(frame: CGRectZero,
collectionViewLayout: UICollectionViewFlowLayout())
/// 关闭按钮
private var closeButton: UIButton = UIButton(title: "关闭",
fontSize: 14,
color: UIColor.whiteColor(),
imageName: nil,
backColor: UIColor.darkGrayColor())
/// 保存按钮
private var saveButton: UIButton = UIButton(title: "保存",
fontSize: 14,
color: UIColor.whiteColor(),
imageName: nil,
backColor: UIColor.darkGrayColor())
- 设置界面
override func loadView() {
let rect = UIScreen.mainScreen().bounds
view = UIView(frame: rect)
setupUI()
}
- 设置界面扩展
// MARK: - 设置界面
private extension PhotoBrowserViewController {
private func setupUI() {
// 1. 添加控件
view.addSubview(collectionView)
view.addSubview(closeButton)
view.addSubview(saveButton)
// 2. 控件布局
collectionView.frame = view.bounds
closeButton.snp_makeConstraints { (make) -> Void in
make.bottom.equalTo(view.snp_bottom).offset(-8)
make.left.equalTo(view.snp_left).offset(8)
make.size.equalTo(CGSize(width: 100, height: 36))
}
saveButton.snp_makeConstraints { (make) -> Void in
make.bottom.equalTo(view.snp_bottom).offset(-8)
make.right.equalTo(view.snp_right).offset(-8)
make.size.equalTo(CGSize(width: 100, height: 36))
}
}
}
- 按钮监听方法
// MARK: - 监听方法
/// 关闭
@objc private func close() {
dismissViewControllerAnimated(true, completion: nil)
}
/// 保存照片
@objc private func save() {
print("保存照片")
}
- 添加按钮监听
// 3. 监听方法
closeButton.addTarget(self, action: "close", forControlEvents: .TouchUpInside)
saveButton.addTarget(self, action: "save", forControlEvents: .TouchUpInside)
数据源方法
- 定义可重用标识符号常量
/// 可重用标识符号
private let PhotoBrowserViewCellId = "PhotoBrowserViewCellId"
- 定义 照片查看 Cell
// MARK: - 照片查看 Cell
private class PhotoBrowserCell: UICollectionViewCell {
}
- 设置数据源
// 4. 准备控件
prepareCollectionView()
}
/// 准备 CollectionView
private func prepareCollectionView() {
// 1. 设置数据源
collectionView.dataSource = self
// 2. 注册可重用 cell
collectionView.registerClass(PhotoBrowserCell.self, forCellWithReuseIdentifier: PhotoBrowserViewCellId)
}
- 利用
extension
扩展数据源方法
// MARK: - UICollectionViewDataSource
extension PhotoBrowserViewController: UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return urls.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserViewCellId, forIndexPath: indexPath) as! PhotoBrowserCell
cell.backgroundColor = UIColor.redColor()
return cell
}
}
- 设置布局
// MARK: - 照片浏览视图布局
private class PhotoBrowserViewLayout: UICollectionViewFlowLayout {
private override func prepareLayout() {
super.prepareLayout()
itemSize = collectionView!.bounds.size
minimumInteritemSpacing = 0
minimumLineSpacing = 0
scrollDirection = .Horizontal
collectionView?.pagingEnabled = true
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.bounces = false
}
}
- 添加
UIColor+Extension
extension UIColor {
/// 随机颜色
class func randomColor() -> UIColor {
let r = CGFloat(random() % 256) / 255
let g = CGFloat(random() % 256) / 255
let b = CGFloat(random() % 256) / 255
return UIColor(red: r, green: g, blue: b, alpha: 1.0)
}
}
- 数据源方法
cell.backgroundColor = UIColor.randomColor()
- 主要内容1. 正则表达式2. 照片查看器
- 2.1. 选择照片事件传递2.2. 照片查看器界面搭建2.3. 照片查看 Cell2.4. 照片间距2.5. 细节处理2.6. 保存照片2.7. 点击照片关闭控制器2.8. 转场动画2.9. 转场进阶
- Published using GitBook
照片浏览器
2.3照片查看 Cell
准备工作
- 新建
PhotoBrowserCell
- 定义内部控件
// MARK: - 照片查看 Cell
class PhotoBrowserCell: UICollectionViewCell {
// MARK: - 构造函数
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
// 1. 添加控件
contentView.addSubview(scrollView)
scrollView.addSubview(imageView)
// 2. 设置位置
scrollView.frame = bounds
}
// MARK: - 懒加载控件
/// 滚动视图
private lazy var scrollView: UIScrollView = UIScrollView()
/// 图像视图
private lazy var imageView: UIImageView = UIImageView()
}
设置图像
- 错误的设置图像
var imageURL: NSURL? {
didSet {
imageView.sd_setImageWithURL(imageURL!)
}
}
设置图像后,imageView 的 frame 没有计算
- 修改设置图像代码
/// 图像 URL
var imageURL: NSURL? {
didSet {
imageView.sd_setImageWithURL(imageURL, placeholderImage: nil) { image, _, _, _ in
self.imageView.sizeToFit()
}
}
}
图像非常小,原因:在微博首页显示的图片是缩略图
- 新建函数处理 imageURL
/// 创建大图 URL
///
/// - parameter url: 缩略图 URL
///
/// - returns: 中图 URL
private func middleUrl(url: NSURL) -> NSURL {
var urlString = url.absoluteString
urlString = urlString.stringByReplacingOccurrencesOfString("/thumbnail/", withString: "/bmiddle/")
return NSURL(string: urlString)!
}
- 修改 didSet 函数
/// 图像 URL
var imageURL: NSURL? {
didSet {
guard let url = imageURL else {
return
}
// 用缩略图当作占位视图
let key = url.absoluteString
imageView.image = SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey(key)
imageView.sizeToFit()
imageView.center = scrollView.center
imageView.sd_setImageWithURL(middleUrl(imageURL!),
placeholderImage: nil) { image, _, _, _ in
delay(1.0, callFunc: { () -> () in
self.imageView.center = CGPointZero
self.imageView.sizeToFit()
})
}
}
}
计算长短图
- 以 cell 宽度为基准计算显示尺寸
/// 计算图像显示大小
///
/// - parameter image: 图像
///
/// - returns: 以 ScrollView 宽度为基准的图像大小
private func displaySize(image: UIImage) -> CGSize {
let w = scrollView.bounds.width
let h = image.size.height * w / image.size.width
return CGSize(width: w, height: h)
}
- 在回调代码中重新设置 imageView 的 size
imageView.sd_setImageWithURL(middleUrl(imageURL!),
placeholderImage: nil) { image, _, _, _ in
if image == nil {
SVProgressHUD.showInfoWithStatus("加载图像失败")
return
}
delay(1.0, callFunc: { () -> () in
self.imageView.center = CGPointZero
self.imageView.frame = CGRect(origin: CGPointZero, size: self.displaySize(image))
})
}
提示:此处一定加上图像加载判断,因为 SDWebImage 的超时时长是 15s,一旦超时,图像就会加载失败
- 计算图片位置
/// 设置图像位置
///
/// * 如果是长图,顶端对齐
/// * 如果是短图,居中显示
private func setImagePosition(image: UIImage) {
// 1. 计算图像显示大小
let size = displaySize(image)
scrollView.contentSize = size
// 2. 判断图像高度
if size.height > scrollView.bounds.height {
imageView.frame = CGRect(origin: CGPointZero, size: size)
} else {
let y = (scrollView.bounds.height - size.height) * 0.5
imageView.frame = CGRect(x: 0, y: y, width: size.width, height: size.height)
}
}
- 长图增加 contentSize 保证图像能够滚动
scrollView.contentSize = size
- 修改
imageURL
的didSet
self.setImagePosition()
运行测试
缩放处理
- 设置滚动视图
/// 准备滚动视图
private func prepareScrollView() {
scrollView.delegate = self
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 2.0
}
- 实现代理方法
// MARK: - UIScrollViewDelegate
extension PhotoBrowserCell: UIScrollViewDelegate {
/// 返回要缩放的视图
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
/// 缩放完成后才回被调用
///
/// - parameter scrollView: scrollView
/// - parameter view: 被缩放的视图
/// - parameter scale: 缩放完成的比例
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
}
// 只要缩放就会被调用
func scrollViewDidZoom(scrollView: UIScrollView) {
}
}
运行测试发现短图放大后,无法滚动显示底部内容
- 调整
setImagePosition
函数,使用contentInset
调整短图的图像视图位置
/// 设置图像位置
///
/// * 如果是长图,顶端对齐
/// * 如果是短图,居中显示
private func setImagePosition(image: UIImage) {
// 1. 计算图像显示大小
let size = displaySize(image)
scrollView.contentSize = size
// 2. 判断图像高度
if size.height > scrollView.bounds.height {
imageView.frame = CGRect(origin: CGPointZero, size: size)
} else {
imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let y = (scrollView.bounds.height - size.height) * 0.5
scrollView.contentInset = UIEdgeInsets(top: y, left: 0, bottom: 0, right: 0)
}
}
- 缩放停止后重新调整 Y 轴位置
/// 缩放完成后才回被调用
///
/// - parameter scrollView: scrollView
/// - parameter view: 被缩放的视图
/// - parameter scale: 缩放完成的比例
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
var offsetY = (scrollView.bounds.height - view!.frame.height) * 0.5
offsetY = offsetY < 0 ? 0 : offsetY
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: 0, bottom: 0, right: 0)
}
注意:使用 transform 修改视图大小时,
bounds
本身不会发生变化,而frame
会发生变化
- 增加水平方向位置调整
/// 缩放完成后才回被调用
///
/// - parameter scrollView: scrollView
/// - parameter view: 被缩放的视图
/// - parameter scale: 缩放完成的比例
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
var offsetY = (scrollView.bounds.height - view!.frame.height) * 0.5
var offsetX = (scrollView.bounds.width - view!.frame.width) * 0.5
offsetY = offsetY < 0 ? 0 : offsetY
offsetX = offsetX < 0 ? 0 : offsetX
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: 0, right: 0)
}
- 图像缩放比例复位,以及滚动视图内容属性
/// 重置滚动视图
private func resetScrollView() {
imageView.transform = CGAffineTransformIdentity
scrollView.contentInset = UIEdgeInsetsZero
scrollView.contentOffset = CGPointZero
scrollView.contentSize = CGSizeZero
}
- 最终完成的 imageURL 属性
/// 图像 URL
var imageURL: NSURL? {
didSet {
guard let url = imageURL else {
return
}
// 重置滚动视图
resetScrollView()
// 用缩略图当作占位视图
let key = url.absoluteString
imageView.image = SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey(key)
imageView.sizeToFit()
imageView.center = scrollView.center
imageView.sd_setImageWithURL(middleUrl(imageURL!),
placeholderImage: nil,
options: [SDWebImageOptions.RetryFailed, SDWebImageOptions.RefreshCached],
progress: { (current, total) -> Void in
dispatch_async(dispatch_get_main_queue()) {
self.imageView.progress = CGFloat(current) / CGFloat(total)
}
}) { (image, _, _, _) in
if image == nil {
SVProgressHUD.showInfoWithStatus("加载图像失败")
return
}
self.setImagePosition(image)
}
}
}