1.保存图片
@objc private func saveBtnClick()
{
// 1.获取当前显示图片的索引
let indexPath = collectionView.indexPathsForVisibleItems().last!
// 2.获取当前显示的cell
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! XMGBrowserCell
// 3.获取当前显示的图片
let image = cell.imageView.image!
// 4.保存图片
// - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
UIImageWriteToSavedPhotosAlbum(image, self, Selector("image:didFinishSavingWithError:contextInfo:"), nil)
}
func image(image:UIImage, didFinishSavingWithError:NSError?, contextInfo: AnyObject?)
{
if didFinishSavingWithError != nil{
SVProgressHUD.showErrorWithStatus("保存图片失败", maskType: SVProgressHUDMaskType.Black)
return
}
SVProgressHUD.showSuccessWithStatus("保存图片成功", maskType: SVProgressHUDMaskType.Black)
}
2.图片缩放动画
/// 监听图片点击通知
@objc private func showBrowser(notice: NSNotification)
{
// 注意: 但凡是通过网络或者通知获取到的数据, 都需要进行安全校验
guard let pictures = notice.userInfo!["bmiddle_pic"] as? [NSURL] else
{
SVProgressHUD.showErrorWithStatus("没有图片", maskType: SVProgressHUDMaskType.Black)
return
}
guard let index = notice.userInfo!["indexPath"] as? NSIndexPath else
{
SVProgressHUD.showErrorWithStatus("没有索引", maskType: SVProgressHUDMaskType.Black)
return
}
guard let pictureView = notice.object as? XMGPictureView else
{
return
}
// 弹出图片浏览器, 将所有图片和当前点击的索引传递给浏览器
let vc = BrowserViewController(bmiddle_pic: pictures, indexPath: index)
// 设置转场动画代理
vc.transitioningDelegate = browserPresentationManager
// 设置转场动画样式
vc.modalPresentationStyle = UIModalPresentationStyle.Custom
// 设置转场需要的其它数据
browserPresentationManager.setDefaultInfo(index, browserDelegate: pictureView)
presentViewController(vc, animated: true, completion: nil)
}
extension XMGPictureView: XMGBrowserPresentationDelegate
{
/// 用于创建一个和点击图片一模一样的UIImageView
func browserPresentationWillShowImageView(browserPresenationController: XMGBrowserPresentationController, indexPath: NSIndexPath) -> UIImageView
{
// 1.创建一个新的UIImageView
let iv = UIImageView()
iv.contentMode = UIViewContentMode.ScaleAspectFill
iv.clipsToBounds = true
// 2.设置UIImageView的图片为点击的图片
// let cell = cellForItemAtIndexPath(indexPath) as! HomePictureCell
// iv.image = cell.customIconImageView.image
let key = viewModel!.bmiddle_pic![indexPath.item].absoluteString
let image = SDWebImageManager.sharedManager().imageCache.imageFromDiskCacheForKey(key)
iv.image = image
iv.sizeToFit()
// 3.返回图片
return iv
}
/// 用于获取点击图片相对于window的frame
func browserPresentationWillFromFrame(browserPresenationController: XMGBrowserPresentationController, indexPath: NSIndexPath) -> CGRect
{
// 1.拿到被点击的cell
let cell = cellForItemAtIndexPath(indexPath) as! HomePictureCell
// 2.将被点击的cell的坐标系从collectionview转换到keywindow
// NJLog(cell.frame)
let frame = self.convertRect(cell.frame, toCoordinateSpace: UIApplication.sharedApplication().keyWindow!)
// NJLog(frame)
return frame
}
/// 用于获取点击图片最终的frame
func browserPresentationWillToFrame(browserPresenationController: XMGBrowserPresentationController, indexPath: NSIndexPath) -> CGRect
{
let width = UIScreen.mainScreen().bounds.width
let height = UIScreen.mainScreen().bounds.height
// 1.拿到被点击的cell
let cell = cellForItemAtIndexPath(indexPath) as! HomePictureCell
// 2.拿到被点击的图片
let image = cell.customIconImageView.image!
// 3.计算当前图片的宽高比
let scale = image.size.height / image.size.width
// 4.利用宽高比乘以屏幕宽度, 等比缩放图片
let imageHeight = scale * width
var offsetY: CGFloat = 0
// 5.判断当前是长图还是短图
if imageHeight < height
{
// 短图
// 4.计算顶部和底部内边距
offsetY = (height - imageHeight) * 0.5
}
return CGRect(x: 0, y: offsetY, width: width, height: imageHeight)
}
}
XMGBrowserPresentationController.swift
import UIKit
protocol XMGBrowserPresentationDelegate: NSObjectProtocol
{
/// 用于创建一个和点击图片一模一样的UIImageView
func browserPresentationWillShowImageView(browserPresenationController: XMGBrowserPresentationController, indexPath: NSIndexPath) -> UIImageView
/// 用于获取点击图片相对于window的frame
func browserPresentationWillFromFrame(browserPresenationController: XMGBrowserPresentationController, indexPath: NSIndexPath) -> CGRect
/// 用于获取点击图片最终的frame
func browserPresentationWillToFrame(browserPresenationController: XMGBrowserPresentationController, indexPath: NSIndexPath) -> CGRect
}
class XMGBrowserPresentationController: UIPresentationController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
/// 定义标记记录当前是否是展现
private var isPresent = false
/// 当前点击图片对应的索引
private var index: NSIndexPath?
/// 代理对象
weak var browserDelegate: XMGBrowserPresentationDelegate?
/// 设置默认数据
func setDefaultInfo(index: NSIndexPath, browserDelegate: XMGBrowserPresentationDelegate)
{
self.index = index
self.browserDelegate = browserDelegate
}
// MARK: - UIViewControllerTransitioningDelegate
// 该方法用于返回一个负责转场动画的对象
// 可以在该对象中控制弹出视图的尺寸等
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
{
return XMGBrowserPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
// 该方法用于返回一个负责转场如何出现的对象
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
isPresent = true
return self
}
// 该方法用于返回一个负责转场如何消失的对象
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
isPresent = false
return self
}
// MARK: - UIViewControllerAnimatedTransitioning
// 告诉系统展现和消失的动画时长
// 暂时用不上
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval
{
return 3.0
}
// 专门用于管理modal如何展现和消失的, 无论是展现还是消失都会调用该方法
func animateTransition(transitionContext: UIViewControllerContextTransitioning)
{
// 0.判断当前是展现还是消失
if isPresent
{
// 展现
willPresentedController(transitionContext)
}else
{
// 消失
willDismissedController(transitionContext)
}
}
/// 执行展现动画
private func willPresentedController(transitionContext: UIViewControllerContextTransitioning)
{
assert(index != nil, "必须设置被点击cell的indexPath")
assert(browserDelegate != nil, "必须设置代理才能展现")
// 1.获取需要弹出视图
// 通过ToViewKey取出的就是toVC对应的view(图片浏览器)
guard let toView = transitionContext.viewForKey(UITransitionContextToViewKey) else
{
return
}
// 2.准备动画
// 2.1.新建一个UIImageView, 并且上面显示的内容必须和被点击的图片一模一样
let imageView = browserDelegate!.browserPresentationWillShowImageView(self, indexPath: index!)
// 2.2.获取点击图片相对于window的frame, 因为容器视图是全屏的, 而图片是添加到容器视图上的, 所以必须获取相对于window的frame
imageView.frame = browserDelegate!.browserPresentationWillFromFrame(self, indexPath: index!)
transitionContext.containerView()?.addSubview(imageView)
// 2.3.获取点击图片最终显示的尺寸
let toFrame = browserDelegate!.browserPresentationWillToFrame(self, indexPath: index!)
// 3.执行动画
UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
imageView.frame = toFrame
}) { (_) -> Void in
// 移除自己添加的UIImageView
imageView.removeFromSuperview()
// 显示图片浏览器
transitionContext.containerView()?.addSubview(toView)
// 告诉系统动画执行完毕
transitionContext.completeTransition(true)
}
}
/// 执行消失动画
private func willDismissedController(transitionContext: UIViewControllerContextTransitioning)
{
transitionContext.completeTransition(true)
}
}
3.网络工具类
import UIKit
import AFNetworking
class NetworkTools: AFHTTPSessionManager {
// Swift推荐我们这样编写单例
static let shareInstance: NetworkTools = {
// 注意: baseURL后面一定更要写上./
let baseURL = NSURL(string: "https://api.weibo.com/")!
let instance = NetworkTools(baseURL: baseURL, sessionConfiguration: NSURLSessionConfiguration.defaultSessionConfiguration())
instance.responseSerializer.acceptableContentTypes = NSSet(objects:"application/json", "text/json", "text/javascript", "text/plain") as Set
return instance
}()
// MARK: - 外部控制方法
func loadStatuses(since_id: String, max_id: String, finished: (array: [[String: AnyObject]]?, error: NSError?)->())
{
assert(UserAccount.loadUserAccount() != nil, "必须授权之后才能获取微博数据")
// 1.准备路径
let path = "2/statuses/home_timeline.json"
// 2.准备参数
let temp = (max_id != "0") ? "\(Int(max_id)! - 1)" : max_id
let parameters = ["access_token": UserAccount.loadUserAccount()!.access_token!, "since_id": since_id, "max_id": temp]
// 3.发送GET请求
GET(path, parameters: parameters, success: { (task, objc) -> Void in
// 返回数据给调用者
guard let arr = (objc as! [String: AnyObject])["statuses"] as? [[String: AnyObject]] else
{
finished(array: nil, error: NSError(domain: "com.520it.lnj", code: 1000, userInfo: ["message": "没有获取到数据"]))
return
}
finished(array: arr, error: nil)
}) { (task, error) -> Void in
finished(array: nil, error: error)
}
}
/// 发送微博
func sendStatus(status: String, finished: (objc: AnyObject?, error: NSError?)->())
{
// 1.准备路径
let path = "2/statuses/update.json"
// 2.准备参数
let parameters = ["access_token": UserAccount.loadUserAccount()!.access_token!, "status": status]
// 3.发送POST请求
POST(path, parameters: parameters, success: { (taks, objc) -> Void in
finished(objc: objc, error: nil)
}) { (task, error) -> Void in
finished(objc: nil, error: error)
}
}
}
4.表情键盘
//
// XMGKeyboardEmoticonCell.swift
// 表情键盘
//
// Created by xiaomage on 15/12/9.
// Copyright © 2015年 xiaomage. All rights reserved.
//
import UIKit
class XMGKeyboardEmoticonCell: UICollectionViewCell {
/// 当前行对应的表情模型
var emoticon: XMGKeyboardEmoticon?
{
didSet{
// 1.显示emoji表情
iconButton.setTitle(emoticon?.emoticonStr ?? "", forState: UIControlState.Normal)
// 2.设置图片表情
iconButton.setImage(nil, forState: UIControlState.Normal)
if emoticon?.chs != nil
{
iconButton.setImage(UIImage(contentsOfFile: emoticon!.pngPath!), forState: UIControlState.Normal)
}
// 3.设置删除按钮
if emoticon!.isRemoveButton
{
iconButton.setImage(UIImage(named: "compose_emotion_delete"), forState: UIControlState.Normal)
iconButton.setImage(UIImage(named: "compose_emotion_delete_highlighted"), forState: UIControlState.Highlighted)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUI()
}
// MARK: - 内部控制方法
private func setupUI()
{
// 1.添加子控件
contentView.addSubview(iconButton)
iconButton.backgroundColor = UIColor.whiteColor()
// 2.布局子控件
iconButton.frame = CGRectInset(bounds, 4, 4)
}
// MARK: - 懒加载
private lazy var iconButton: UIButton = {
let btn = UIButton()
btn.userInteractionEnabled = false
btn.titleLabel?.font = UIFont.systemFontOfSize(30)
return btn
}()
}
import UIKit
class XMGKeyboardEmoticonViewController: UIViewController {
/// 保存所有组数据
var packages: [XMGKeyboardPackage] = XMGKeyboardPackage.loadEmotionPackages()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.redColor()
// 1.添加子控件
view.addSubview(collectionView)
view.addSubview(toolbar)
collectionView.translatesAutoresizingMaskIntoConstraints = false
toolbar.translatesAutoresizingMaskIntoConstraints = false
// 2.布局子控件
let dict = ["collectionView": collectionView, "toolbar": toolbar]
var cons = NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[collectionView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
cons += NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[toolbar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
cons += NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[collectionView]-[toolbar(49)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
view.addConstraints(cons)
}
// MARK: - 内部控制方法
@objc private func itemClick(item: UIBarButtonItem)
{
// 1.创建indexPath
let indexPath = NSIndexPath(forItem: 0, inSection: item.tag)
// 2.滚动到指定的indexPath
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
}
// MARK: - 懒加载
private lazy var collectionView: UICollectionView = {
let clv = UICollectionView(frame: CGRectZero, collectionViewLayout: XMGKeyboardEmoticonLayout())
clv.backgroundColor = UIColor.greenColor()
clv.dataSource = self
clv.delegate = self
clv.registerClass(XMGKeyboardEmoticonCell.self, forCellWithReuseIdentifier: "keyboardCell")
return clv
}()
private lazy var toolbar: UIToolbar = {
let tb = UIToolbar()
tb.tintColor = UIColor.lightGrayColor()
var items = [UIBarButtonItem]()
var index = 0
for title in ["最近", "默认", "Emoji", "浪小花"]
{
// 1.创建item
let item = UIBarButtonItem(title: title, style: UIBarButtonItemStyle.Plain, target: self, action: Selector("itemClick:"))
item.tag = index++
items.append(item)
// 2.创建间隙
let flexibleItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
items.append(flexibleItem)
}
items.removeLast()
// 2.将item添加到toolbar上
tb.items = items
return tb
}()
}
extension XMGKeyboardEmoticonViewController: UICollectionViewDataSource
{
// 告诉系统有多少组
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return packages.count
}
// 告诉系统每组多少个
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return packages[section].emoticons?.count ?? 0
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// 1.取出cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("keyboardCell", forIndexPath: indexPath) as! XMGKeyboardEmoticonCell
cell.backgroundColor = (indexPath.item % 2 == 0) ? UIColor.redColor(): UIColor.purpleColor()
// 2.设置数据
// cell.emoticon = packages[indexPath.section].emoticons![indexPath.item]
let package = packages[indexPath.section]
cell.emoticon = package.emoticons![indexPath.item]
// 3.返回cell
return cell
}
}
extension XMGKeyboardEmoticonViewController: UICollectionViewDelegate
{
/// 监听表情点击
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let package = packages[indexPath.section]
let emoticon = package.emoticons![indexPath.item]
print(emoticon.chs)
// 每使用一次就+1
emoticon.count += 1
// 判断是否是删除按钮
if !emoticon.isRemoveButton
{
// 将当前点击的表情添加到最近组中
packages[0].addFavoriteEmoticon(emoticon)
}
}
}
class XMGKeyboardEmoticonLayout: UICollectionViewFlowLayout {
override func prepareLayout() {
super.prepareLayout()
// 1.计算cell宽度
let width = UIScreen.mainScreen().bounds.width / 7
let height = collectionView!.bounds.height / 3
itemSize = CGSize(width: width, height: height)
minimumInteritemSpacing = 0
minimumLineSpacing = 0
scrollDirection = UICollectionViewScrollDirection.Horizontal
// 2.设置collectionView
collectionView?.bounces = false
collectionView?.pagingEnabled = true
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.showsVerticalScrollIndicator = false
}
}
import UIKit
/**
说明:
1. Emoticons.bundle 的根目录下存放的 emoticons.plist 保存了 packages 表情包信息
>packages 是一个数组, 数组中存放的是字典
>字典中的属性 id 对应的分组路径的名称
2. 在 id 对应的目录下,各自都保存有 info.plist
>group_name_cn 保存的是分组名称
>emoticons 保存的是表情信息数组
>code UNICODE 编码字符串
>chs 表情文字,发送给新浪微博服务器的文本内容
>png 表情图片,在 App 中进行图文混排使用的图片
*/
class XMGKeyboardPackage: NSObject {
/// 当前组的名称
var group_name_cn: String?
/// 当前组对应的文件夹名称
var id: String?
/// 当前组所有的表情
var emoticons: [XMGKeyboardEmoticon]?
init(id: String) {
self.id = id
}
/// 加载所有组数据
class func loadEmotionPackages() -> [XMGKeyboardPackage] {
var models = [XMGKeyboardPackage]()
// 0.手动添加最近组
let package = XMGKeyboardPackage(id: "")
package.appendEmptyEmoticons()
models.append(package)
// 1.加载emoticons.plist文件
// 1.1获取emoticons.plist文件路径
let path = NSBundle.mainBundle().pathForResource("emoticons.plist", ofType: nil, inDirectory: "Emoticons.bundle")!
// 1.2加载emoticons.plist
let dict = NSDictionary(contentsOfFile: path)!
let array = dict["packages"] as! [[String: AnyObject]]
// 2.取出所有组表情
for packageDict in array
{
// 2.1创建当前组模型
let package = XMGKeyboardPackage(id: packageDict["id"] as! String)
// 2.2加载当前组所有的表情数据
package.loadEmoticons()
// 2.3补全一组数据, 保证当前组能被21整除
package.appendEmptyEmoticons()
// 2.4将当前组模型添加到数组中
models.append(package)
}
return models
}
/// 加载当前组所有表情
private func loadEmoticons()
{
// 1.拼接当前组info.plist路径
let path = NSBundle.mainBundle().pathForResource(self.id, ofType: nil, inDirectory: "Emoticons.bundle")!
let filePath = (path as NSString).stringByAppendingPathComponent("info.plist")
// 2.根据路径加载info.plist文件
let dict = NSDictionary(contentsOfFile: filePath)!
// 3.从加载进来的字典中取出当前组数据
// 3.1取出当前组名称
group_name_cn = dict["group_name_cn"] as? String
// 3.2取出当前组所有表情
let array = dict["emoticons"] as! [[String: AnyObject]]
// 3.3遍历数组, 取出每一个表情
var models = [XMGKeyboardEmoticon]()
var index = 0
for emoticonDict in array
{
if index == 20
{
let emoticon = XMGKeyboardEmoticon(isRemoveButton: true)
models.append(emoticon)
index = 0
continue
}
let emoticon = XMGKeyboardEmoticon(dict: emoticonDict, id: self.id!)
models.append(emoticon)
index++
}
emoticons = models
}
/// 补全一组数据, 保证当前组能被21整除
private func appendEmptyEmoticons()
{
// 0.判断是否是最近组
if emoticons == nil
{
emoticons = [XMGKeyboardEmoticon]()
}
// 1.取出不能被21整除剩余的个数
let number = emoticons!.count % 21
// print(emoticons!.count)
// print(number)
// 2.补全
for _ in number..<20
{
let emoticon = XMGKeyboardEmoticon(isRemoveButton: false)
emoticons?.append(emoticon)
}
// print(emoticons!.count)
// 3.补全删除按钮
let emoticon = XMGKeyboardEmoticon(isRemoveButton: true)
emoticons?.append(emoticon)
}
/// 添加最近表情
func addFavoriteEmoticon(emoticon: XMGKeyboardEmoticon)
{
emoticons?.removeLast()
// 1.判断当前表情是否已经添加过了
if !emoticons!.contains(emoticon)
{
// 2.添加当前点击的表情到最近
emoticons?.removeLast()
emoticons?.append(emoticon)
}
// 3.对表情进行排序
let array = emoticons?.sort({ (e1, e2) -> Bool in
return e1.count > e2.count
})
emoticons = array
// 4.添加一个删除按钮
emoticons?.append(XMGKeyboardEmoticon(isRemoveButton: true))
}
}
class XMGKeyboardEmoticon: NSObject {
/// 当前组对应的文件夹名称
var id: String?
/// 当前表情对应的字符串
var chs: String?
/// 当前表情对应的图片
var png: String?
{
didSet
{
let path = NSBundle.mainBundle().pathForResource(id, ofType: nil, inDirectory: "Emoticons.bundle")!
pngPath = (path as NSString).stringByAppendingPathComponent(png ?? "")
}
}
/// 当前表情图片的绝对路径
var pngPath: String?
/// Emoji表情对应的字符串
var code: String?
{
didSet
{
// 1.创建一个扫描器
let scanner = NSScanner(string: code ?? "")
// 2.从字符串中扫描出对应的16进制数
var result: UInt32 = 0
scanner.scanHexInt(&result)
// 3.根据扫描出的16进制创建一个字符串
emoticonStr = "\(Character(UnicodeScalar(result)))"
}
}
/// 转换之后的emoji表情字符串
var emoticonStr: String?
/// 记录当前表情是否是删除按钮
var isRemoveButton: Bool = false
/// 记录当前表情的使用次数
var count: Int = 0
init(dict: [String: AnyObject], id: String)
{
self.id = id
super.init()
setValuesForKeysWithDictionary(dict)
}
init(isRemoveButton: Bool)
{
self.isRemoveButton = isRemoveButton
}
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}
}