分享

iOS高仿爱鲜蜂

 宇智波瞬润 2016-06-02

关于项目(代码下载地址在文章最下面点击GitHub链接)

本次开源项目为爱鲜蜂,一款电商APP,使用语言Swift2.0,开发工具Xcode7.0.1.

项目为纯代码开发,没有使用XIB和StoryBoard.开发周期大概为2个月左右(工作闲暇之余).

数据都是本地数据,辅助开发软件:PhotoShop CS6(图片处理),Charles(抓包工具).

写的比较匆忙,很多地方无法尽善尽美,如果有建议和可优化的地方可在文章底部留言,我会一一查看的并回复的.

项目效果图

 

效果图1
 

效果图2
 

效果图3
 

效果图4
 

效果图5
 

效果图6

项目详细讲解(根据启动流程)

引导页和AD(广告)页

当程序被打开时,在创建KeyWindow的RootViewController时判断是否是首次登陆,这里的逻辑是如果用户是首次打开应用的话显示引导页,当点击引导页最后一页的立即体验直接进入TabBarController,不显示广告页(效果如下图)

 

引导页

如果用户不是首次打开应用的话,则显示广告页,并且在4秒后以放大并且透明的效果进入TabBarController(效果如下图)

 

广告页

逻辑代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // MARK: - Public Method
    private func buildKeyWindow() {
        window = UIWindow(frame: ScreenBounds)
        window!.makeKeyAndVisible()
        let isFristOpen = NSUserDefaults.standardUserDefaults().objectForKey("isFristOpenApp")
        if isFristOpen == nil {
            window?.rootViewController = GuideViewController()
            NSUserDefaults.standardUserDefaults().setObject("isFristOpenApp", forKey: "isFristOpenApp")
        } else {
            loadADRootViewController()
        }
    }

引导页考虑到循环利用,使用的是UICollectionView实现

AD(广告)页需要注意的是ADViewController有时会存在加载广告图片失败的情况,如果加载失败,发送加载图片失败的通知,window直接将tarBarController作为keyWindow即可

关于引导页和AD页的实现代码,我就不详细讲述了,源代码都有,有兴趣的读者可以打开代码自行研究

AnimationTabBarController(带有动画的TabBarItem)

这里有个小故事,我是无意中发现爱鲜蜂底部TabBarItem有点击的动画,感觉挺有意思的,尝试着自己实现了同样的功能,然后才突发奇将后续的功能都给实现了.先看下底部UITabBarItem的动画(效果如下图)

 

TaBarItem动画效果

底部的TabBarItem动画使用了三方框架RAMAnimatedTabBar,由于原来的框架 只能通过StoryBoard初始化控件,并且无法满足项目需求,所以对框架进行了大量修改,其原理很简单,就是在TabBarController初始化时,通过拦截Items,重新创建一套相同的View,并且在每个View上添加ImageView和Label,在View的点击事件中,控制动画即可.用这种方式也可以轻松完成一些看似复杂的动画,如下图所示,其实通过ImageView的序列动画就可以轻松完成,只是需要在AE中做出动画序列图即可.

 

通过序列动画达到的效果

首页

首页由三部分构成,顶部的轮播图(PageScrollView),轮播图下面的活动按钮,以及UICollectionView.(如下图所示)

 

首页效果图

其中将PageScrollView与活动按钮封装成了UICollectionView的headView,通过代理方法将点击的事件,需要注意的是活动按钮并不是固定只有四个,这里是根据服务器返回的数据创建的,有时候会有多个,需要根据数量控制列数,通过代理方法将高度传给首页的控制器.

PageScrollView(轮播图)

这里采用循环利用机制写的,只创建3个ImageView即可,在同一时刻屏幕中最多只会显示2个ImageView,当需要展示新的ImageView,只需要将缓存数组中的没有展示的ImageView拿出来展示即可,这里为了方便大家将项目移植到自己的项目中使用,我将代码全部拷贝过来了,需要替换数据修改headData内的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import UIKit
class PageScrollView: UIView {
    private let imageViewMaxCount = 3
    private var imageScrollView: UIScrollView!
    private var pageControl: UIPageControl!
    private var timer: NSTimer?
    private var placeholderImage: UIImage?
    private var imageClick:((index: Int) -> ())?
    var headData: HeadResources? {
        didSet {
            if timer != nil {
                timer!.invalidate()
                timer = nil
            }
            if headData?.data?.focus?.count >= 0 {
                pageControl.numberOfPages = (headData?.data?.focus?.count)!
                pageControl.currentPage = 0
                updatePageScrollView()
                startTimer()
            }
        }
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        buildImageScrollView()
        buildPageControl()
    }
    convenience init(frame: CGRect, placeholder: UIImage, focusImageViewClick:((index: Int) -> Void)) {
        self.init(frame: frame)
        placeholderImage = placeholder
        imageClick = focusImageViewClick
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        imageScrollView.frame = bounds
        imageScrollView.contentSize = CGSizeMake(CGFloat(imageViewMaxCount) * width, 0)
        for i in 0...imageViewMaxCount - 1 {
            let imageView = imageScrollView.subviews[i] as! UIImageView
            imageView.userInteractionEnabled = true
            imageView.frame = CGRectMake(CGFloat(i) * imageScrollView.width, 0, imageScrollView.width, imageScrollView.height)
        }
        let pageW: CGFloat = 80
        let pageH: CGFloat = 20
        let pageX: CGFloat = imageScrollView.width - pageW
        let pageY: CGFloat = imageScrollView.height - pageH
        pageControl.frame = CGRectMake(pageX, pageY, pageW, pageH)
        updatePageScrollView()
    }
    // MARK: BuildUI
    private func buildImageScrollView() {
        imageScrollView = UIScrollView()
        imageScrollView.bounces = false
        imageScrollView.showsHorizontalScrollIndicator = false
        imageScrollView.showsVerticalScrollIndicator = false
        imageScrollView.pagingEnabled = true
        imageScrollView.delegate = self
        addSubview(imageScrollView)
        for _ in 0..<3 {
            let imageView = UIImageView()
            let tap = UITapGestureRecognizer(target: self, action: "imageViewClick:")
            imageView.addGestureRecognizer(tap)
            imageScrollView.addSubview(imageView)
        }
    }
    private func buildPageControl() {
        pageControl = UIPageControl()
        pageControl.hidesForSinglePage = true
        pageControl.pageIndicatorTintColor = UIColor(patternImage: UIImage(named: "v2_home_cycle_dot_normal")!)
        pageControl.currentPageIndicatorTintColor = UIColor(patternImage: UIImage(named: "v2_home_cycle_dot_selected")!)
        addSubview(pageControl)
    }
    //MARK: 更新内容
    private func updatePageScrollView() {
        for var i = 0; i < imageScrollView.subviews.count; i++ {    
            let imageView = imageScrollView.subviews[i] as! UIImageView
            var index = pageControl.currentPage
            if i == 0 {
                index--
            } else if 2 == i {
                index++
            }
            if index < 0 {
                index = self.pageControl.numberOfPages - 1
            } else if index >= pageControl.numberOfPages {
                index = 0
            }
            imageView.tag = index
            if headData?.data?.focus?.count > 0 {
                imageView.sd_setImageWithURL(NSURL(string: headData!.data!.focus![index].img!), placeholderImage: placeholderImage)
            }
        }
        imageScrollView.contentOffset = CGPointMake(imageScrollView.width, 0)
    }
    // MARK: Timer
    private func startTimer() {
        timer = NSTimer(timeInterval: 3.0, target: self, selector: "next", userInfo: nil, repeats: true)
        NSRunLoop.mainRunLoop().addTimer(timer!, forMode: NSRunLoopCommonModes)
    }
    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
    func next() {
        imageScrollView.setContentOffset(CGPointMake(2.0 * imageScrollView.frame.size.width, 0), animated: true)
    }
    // MARK: ACTION
    func imageViewClick(tap: UITapGestureRecognizer) {
        if imageClick != nil {
            imageClick!(index: tap.view!.tag)
        }
    }
}
// MARK:- UIScrollViewDelegate
extension PageScrollView: UIScrollViewDelegate {
    func scrollViewDidScroll(scrollView: UIScrollView) {
        var page: Int = 0
        var minDistance: CGFloat = CGFloat(MAXFLOAT)
        for i in 0..<imageScrollView.subviews.count {
            let imageView = imageScrollView.subviews[i] as! UIImageView
            let distance:CGFloat = abs(imageView.x - scrollView.contentOffset.x)
            if distance < minDistance {
                minDistance = distance
                page = imageView.tag
            }
        }
        pageControl.currentPage = page
    }
    func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        stopTimer()
    }
    func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        startTimer()
    }
    func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        updatePageScrollView()
    }
    func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
        updatePageScrollView()
    }
}

首页UICollectionView

首页的CollectionView采用了两种Cell,一种是只有ImageView的Cell,一种是商品的Cell(如图所示)

 

首页Cell样式一
 

首页Cell样式二

通过判断indexPath.section展示对应Cell即可.

新Cell出现的停靠动画,如图

 

停靠动画

通过实现UICollectionViewDelegate,在willDisplayCell代理方法完成动画,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
        if indexPath.section == 0 && (indexPath.row == 0 || indexPath.row == 1) {
            return
        }
        if isAnimation {
            startAnimation(cell, offsetY: 80, duration: 1.0)
        }
    }
    private func startAnimation(view: UIView, offsetY: CGFloat, duration: NSTimeInterval) {
        view.transform = CGAffineTransformMakeTranslation(0, offsetY)
        UIView.animateWithDuration(duration, animations: { () -> Void in
            view.transform = CGAffineTransformIdentity
        })
    }

添加商品动画

当用户点击加号时,会出现如下如所示动画

 

添加商品到购物车动画效果

添加商品到购物车基于CoreAnimation(核心动画)实现,通过对ImageView的layer添加缩放,透明度以及路径动画实现.代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import UIKit
class AnimationViewController: BaseViewController {
    var animationLayers: [CALayer]?
    var animationBigLayers: [CALayer]?
    // MARK: 商品添加到购物车动画
    func addProductsAnimation(imageView: UIImageView) {
        if (self.animationLayers == nil)
        {
            self.animationLayers = [CALayer]();
        }
        let frame = imageView.convertRect(imageView.bounds, toView: view)
        let transitionLayer = CALayer()
        transitionLayer.frame = frame
        transitionLayer.contents = imageView.layer.contents
        self.view.layer.addSublayer(transitionLayer)
        self.animationLayers?.append(transitionLayer)
        let p1 = transitionLayer.position;
        let p3 = CGPointMake(view.width - view.width / 4 - view.width / 8 - 6, self.view.layer.bounds.size.height - 40);
        let positionAnimation = CAKeyframeAnimation(keyPath: "position")
        let path = CGPathCreateMutable();
        CGPathMoveToPoint(path, nil, p1.x, p1.y);
        CGPathAddCurveToPoint(path, nil, p1.x, p1.y - 30, p3.x, p1.y - 30, p3.x, p3.y);
        positionAnimation.path = path;
        let opacityAnimation = CABasicAnimation(keyPath: "opacity")
        opacityAnimation.fromValue = 1
        opacityAnimation.toValue = 0.9
        opacityAnimation.fillMode = kCAFillModeForwards
        opacityAnimation.removedOnCompletion = true
        let transformAnimation = CABasicAnimation(keyPath: "transform")
        transformAnimation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
        transformAnimation.toValue = NSValue(CATransform3D: CATransform3DScale(CATransform3DIdentity, 0.2, 0.2, 1))
        let groupAnimation = CAAnimationGroup()
        groupAnimation.animations = [positionAnimation, transformAnimation, opacityAnimation];
        groupAnimation.duration = 0.8
        groupAnimation.delegate = self;
        transitionLayer.addAnimation(groupAnimation, forKey: "cartParabola")
    }
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if self.animationLayers?.count > 0 {
            let transitionLayer = animationLayers![0]
            transitionLayer.hidden = true
            transitionLayer.removeFromSuperlayer()
            animationLayers?.removeFirst()
            view.layer.removeAnimationForKey("cartParabola")
        }
    }
}

闪电超市

闪电超市很明显由有2个TableView构成,如图所示

 

闪电超市效果图

这里采用了2个控制器分别管理各自的TableView,将TableView2添加到VC2上,将VC2.view添加到VC1.view上,然后再通过VC1.addChildViewController将VC2添加到VC1的子控制器中,这样既降低了代码的复杂性,有提升了代码维护性,各自管理各自的TableView.

很多联动的操作都是通过UITableViewDelegate中实现的,有兴趣的同学可参照代码自行研究.

购物车

购物车采用了modal的形式出现.样式上有两种情况,当购物车里没有商品时,购物车显示为空(如下图)

 

当购物车为空

当购物车中有商品时候,显示商品信息(如下图)

 

有商品时的购物车

这里封装了一个UserShopCar单利类,专门用来管理用户购物车,保存用户添加到购物车的商品种类,商品总数,商品价格等,在购物车VC将要出现时候,判断购物车是否为空,如果为空则显示去逛逛,如果不为空则显示商品的信息.内部细节请参考代码

购物车上红色圆圈

购物车红色圆圈也是通过单利类来实现的,内部提供俩个方法,添加商品和移除商品,方法内部包含动画效果,当添加商品或者减掉商品时,调用对象对应的方法即可.

我的

先看下效果

 

我的效果图

我的页面是项目中最为复杂的页面,包含了许多效果.我大体讲一下思路,我的页面是由顶部的View以及一个TableView构成,TableView有一个headView,分别是我的订单,优惠劵以及我的消息,通过闭包的回调完成点击的事件.这个页面比较简单,不过多叙述了.

右上角设置按钮

 

设置效果图

设置页面没有使用TableView,单纯的用View搭建的.

清理缓存这稍微说一下吧,同样也封装了一个工具类,提供四个方法,分别是参看单个文件的大小,查看全部文件大小文件大小,同步将文件夹清除以及异步清除文件夹.有需要的同学可以直接copy走,那去使用,path是文件的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import UIKit
class FileTool: NSObject {
    static let fileManager = NSFileManager.defaultManager()
    /// 计算单个文件的大小
    class func fileSize(path: String) -> Double {
        if fileManager.fileExistsAtPath(path) {
            var dict = try? fileManager.attributesOfItemAtPath(path)
            if let fileSize = dict![NSFileSize] as? Int{
                return Double(fileSize) / 1024.0 / 1024.0
            }
        }
        return 0.0
    }
    /// 计算整个文件夹的大小
    class func folderSize(path: String) -> Double {
        var folderSize: Double = 0
        if fileManager.fileExistsAtPath(path) {
            let chilerFiles = fileManager.subpathsAtPath(path)
            for fileName in chilerFiles! {
                let tmpPath = path as NSString
                let fileFullPathName = tmpPath.stringByAppendingPathComponent(fileName)
                folderSize += FileTool.fileSize(fileFullPathName)
            }
            return folderSize
        }
        return 0
    }
    /// 清除文件 同步
    class func cleanFolder(path: String, complete:(str: String) -> ()) {
        var str: String?
        let chilerFiles = self.fileManager.subpathsAtPath(path)
        for fileName in chilerFiles! {
            let tmpPath = path as NSString
            let fileFullPathName = tmpPath.stringByAppendingPathComponent(fileName)
            if self.fileManager.fileExistsAtPath(fileFullPathName) {
                do {
                    try self.fileManager.removeItemAtPath(fileFullPathName)
                    str = "清理成功"
                } catch _ {
                    str = "清理失败"
                }
            }
        }
        complete(str: str!)
    }
    /// 清除文件 异步
    class func cleanFolderAsync(path: String, complete:(str: String) -> ()) {
        var str: String?
        let queue = dispatch_queue_create("cleanQueue", nil)
        dispatch_async(queue) { () -> Void in
            let chilerFiles = self.fileManager.subpathsAtPath(path)
            for fileName in chilerFiles! {
                let tmpPath = path as NSString
                let fileFullPathName = tmpPath.stringByAppendingPathComponent(fileName)
                if self.fileManager.fileExistsAtPath(fileFullPathName) {
                    do {
                        try self.fileManager.removeItemAtPath(fileFullPathName)
                        str = "清理成功"
                    } catch _ {
                        str = "清理失败"
                    }
                }
            }
            complete(str: str!)
        }
    }
}

我的订单

我的订单由2个控制器构成,分别是MyOrderViewController(我的订单)和OrderViewDetailViewController(订单详情)

 

我的订单效果
我的订单

一个TableView搞定,cell的样式也并不复杂,这里有个小细节就是需要判断订单商品种类的个数,如果是4个以上,只显示五张图片,并且第五张图片以…图片显示,在设置cell的model时,判断商品种类个数即可实现.发福利等按钮是根据服务器返回的数据创建的,不同类型的Button会有不同的Type,type的值为int类型的,这里可以将button的tag设置对应的typr,这点击的时候判断button的tag,通过Swith语法执行对应的操作就可以了.

订单详情页

订单详情页有两部分,分别是订单状态以及订单详情,通过导航栏的titleView(也就是UISegmentedControl)来切换显示不同界面.

订单详情页也是一个TableView就可以搞定,服务器返回是一个数组,这里的逻辑是,当前状态是0时,圆形图片为黄色,并且没有上面的线,最下面的状态没有下边的线,这里的做法是给cell的model赋值的时候,将indexPath一同传入给Cell,判断indexPath.row是多少,如果是0,就将圆形图片显示为黄色,并且隐藏上半部分线,同理当indexPath.row等于状态数组的count-1时,隐藏下半部分的线即可搞定.

订单详情也是通过TableView实现的,采用tableView是考虑到商品种类的cell可以循环利用,顶部的订单细信息和收货人地址为TableHeadView(效果如下图)

 

订单详情结构

底部评价为tableFootView(效果如下图)

 

订单详情结构

优惠劵

 

优惠劵效果图

优惠劵也是由TableView构成的,有两种cell,一种是可以使用的优惠劵,另一种为不可使用的优惠劵,这里通过模型判断cell的展示的类型.

使用规则为H5页面,直接在webView上loadURL就OK了.

我的消息

 

我的消息效果图

依然是tableView,不过这里会根据用户操作动态改变cell的高度.做法是在给cell设置模型的同时,计算出cell全部展示的高度,在模型中创建辅助参数,保存cell的真实高度以及未打开时的高度,在UITableViewDelegate获取cell的高度时,判断当前cell的状态,根据状态返回对应的高度.

cell内部的显示全部按钮通过闭包回调告诉控制器点击事件,同时将cell的IndexPath作为参数传出来,当用户点击显示全部时,根据当前cell的状态取反,同时tableView.reloadData就动态的改变cell的高度了.

我的收货地址

 

我的收货地址效果图

额,还是tableView,不说了.

编辑我的地址有两种情况,一种是修改现有的收货地址,进入时对应的选项都已经存在,并且有删除当前地址的View在底部.另外一种是添加新地址,没有参数和删除当前地址view,这里在EditAdressViewController写一个枚举,并且搞一个成员变量type

1
2
3
4
enum EditAdressViewControllerType: Int {
    case Add
    case Edit
}

在push进入EditAdressViewController时,将EditAdressViewController的类型传入,根据type的类型,显示对应的数据即可.

常见问题

效果如下图:

 

常见问题效果图

常见问题这里UI的搭建相信读者都了然于心,不过多介绍,只讲一下点击出现动画的逻辑.这里也是一个TableView,常见问题为tableView的headView,详细问题View为tableView的Cell,默认cell的个数为零,当用户点击了headView,记录点击的headView的indexPath.section,刷新tableView,将点击行的Cell个数返回为1,并且单独给这一行的cell返回cell的高度.代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
extension HelpDetailViewController: UITableViewDelegate, UITableViewDataSource, HelpHeadViewDelegate {
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = AnswerCell.answerCell(tableView)
        cell.question = questions![indexPath.section]
        return cell
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if lastOpenIndex == section && isOpenCell {
            return 1
        }
        return 0
    }
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if lastOpenIndex == indexPath.section && isOpenCell {
            return questions![indexPath.section].cellHeight
        }
        return 0
    }
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return questions?.count ?? 0
    }
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headView = tableView.dequeueReusableHeaderFooterViewWithIdentifier("headView") as? HelpHeadView
        headView!.tag = section
        headView?.delegate = self
        let question = questions![section]
        headView?.question = question
        return headView!
    }
    func headViewDidClck(headView: HelpHeadView) {
        if lastOpenIndex != -1 && lastOpenIndex != headView.tag && isOpenCell {
            let headView = questionTableView?.headerViewForSection(lastOpenIndex) as? HelpHeadView
            headView?.isSelected = false
            let deleteIndexPaths = [NSIndexPath(forRow: 0, inSection: lastOpenIndex)]
            isOpenCell = false
            questionTableView?.deleteRowsAtIndexPaths(deleteIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)
        }
        if lastOpenIndex == headView.tag && isOpenCell {
            let deleteIndexPaths = [NSIndexPath(forRow: 0, inSection: lastOpenIndex)]
            isOpenCell = false
            questionTableView?.deleteRowsAtIndexPaths(deleteIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)
            return
        }
        lastOpenIndex = headView.tag
        isOpenCell = true
        let insertIndexPaths = [NSIndexPath(forRow: 0, inSection: headView.tag)]
        questionTableView?.insertRowsAtIndexPaths(insertIndexPaths, withRowAnimation: UITableViewRowAnimation.Top)
    }
}

其他功能

三方分享分享

这里我使用了友盟分享SDK,需要在真机上才可以分享,不过Sina微博需要在后台配置测试账号,大家可能无法测试新浪微博分享~,关于分享也是封装了ShareManager工具类,定义好分享枚举类型

1
2
3
4
5
6
enum ShareType: Int {
    case WeiXinMyFriend = 1
    case WeiXinCircleOfFriends = 2
    case SinaWeiBo = 3
    case QQZone = 4
}

将弹出的样式也ActionSheet也封装成单个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class LFBActionSheet: NSObject, UIActionSheetDelegate {
    private var selectedShaerType: ((shareType: ShareType) -> ())?
    private var actionSheet: UIActionSheet?
    func showActionSheetViewShowInView(inView: UIView, selectedShaerType: ((shareType: ShareType) -> ())) {
        actionSheet = UIActionSheet(title: "分享到",
            delegate: self, cancelButtonTitle: "取消",
            destructiveButtonTitle: nil,
            otherButtonTitles: "微信好友", "微信朋友圈", "新浪微博", "QQ空间")
        self.selectedShaerType = selectedShaerType
        actionSheet?.showInView(inView)
    }
    func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {
        print(buttonIndex)
        if selectedShaerType != nil {
            switch buttonIndex {
            case ShareType.WeiXinMyFriend.rawValue:
                selectedShaerType!(shareType: .WeiXinMyFriend)
                break
            case ShareType.WeiXinCircleOfFriends.rawValue:
                selectedShaerType!(shareType: .WeiXinCircleOfFriends)
                break
            case ShareType.SinaWeiBo.rawValue:
                selectedShaerType!(shareType: .SinaWeiBo)
                break
            case ShareType.QQZone.rawValue:
                selectedShaerType!(shareType: .QQZone)
                break
            default:
                break
            }
        }
    }
}

当外部需要调用分享的时候,只需要调用一句代码即可

1
2
3
     shareActionSheet.showActionSheetViewShowInView(view) { (shareType) -> () in
        ShareManager.shareToShareType(shareType, vc: self)
    }

扫一扫

 

扫一扫效果图

注意需要在真机上才可以测试,模拟器没有摄像头的.这里用的iOS7.0以后苹果自带框架AVFoundation,使用非常简单,这也就不过多叙述,讲一下如何实现中间区域亮,四边为黑色的效果,其实原理很简单,在view上创建四个view,如下图

 

将View的背景色改为黑色,透明度为0.5,添加到View上,搞定.
设置captureMetadataOutput.rectOfInterest的范围,控制扫描区域的敏感范围.

搜索控制器

效果如下

 

搜索效果图

搜索控制器导航栏上的搜索条使用的UISearchBar,下面为按钮,需要动态的布局按钮的位置,这里有热搜索和历史搜索,考虑到复用性,将搜索View封装成一个View,在便利构造方法中将按钮的名字数组传入,自定在内部布局,计算高度,通过闭包回调将按钮点击的事件通知给控制器,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import UIKit
class SearchView: UIView {
    private let searchLabel = UILabel()
    private var lastX: CGFloat = 0
    private var lastY: CGFloat = 35
    private var searchButtonClickCallback:((sender: UIButton) -> ())?
    var searchHeight: CGFloat = 0
    override init(frame: CGRect) {
        super.init(frame: frame)
        searchLabel.frame = CGRectMake(0, 0, frame.size.width - 30, 35)
        searchLabel.font = UIFont.systemFontOfSize(15)
        searchLabel.textColor = UIColor.colorWithCustom(140, g: 140, b: 140)
        addSubview(searchLabel)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    convenience init(frame: CGRect, searchTitleText: String, searchButtonTitleTexts: [String], searchButtonClickCallback:((sender: UIButton) -> ())) {
        self.init(frame: frame)
        searchLabel.text = searchTitleText
        var btnW: CGFloat = 0
        let btnH: CGFloat = 30
        let addW: CGFloat = 30
        let marginX: CGFloat = 10
        let marginY: CGFloat = 10
        for i in 0.. btnW {
                btn.frame = CGRectMake(lastX, lastY, btnW, btnH)
            } else {
                btn.frame = CGRectMake(0, lastY + marginY + btnH, btnW, btnH)
            }
            lastX = CGRectGetMaxX(btn.frame) + marginX
            lastY = btn.y
            searchHeight = CGRectGetMaxY(btn.frame)
            addSubview(btn)
        }
        self.searchButtonClickCallback = searchButtonClickCallback
    }
    func searchButtonClick(sender: UIButton) {
        if searchButtonClickCallback != nil {
            searchButtonClickCallback!(sender: sender)
        }
    }
}

唠叨一下

关于项目的内容,并不是短短几千文字就能给大家讲明白的,想要了解更多内容,请打开代码仔细研究,小熊还是抱着为了大家能看懂的套路基本没有使用三方框架,应小东同学的要求使用了纯代码开发.希望对大家有所帮助.记着点个Star哈~

最近有点忙,加上整个人回到北京好像变懒了T_T,有段日子没有发布什么资源了.这个项目就作为新年礼物送给大家吧,还有两个小时,开网小熊回家的列车就出发了,在这里提前祝大家新年快乐~

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多