iOS高仿愛鮮蜂

iOS高仿愛鮮蜂

前言

2015年匆匆的就過去了,又老了一歲,這一年起起伏伏,有笑聲也有眼淚,感謝陪伴在我身邊的人.

關(guān)于項(xiàng)目(代碼下載地址在文章最下面點(diǎn)擊GitHub鏈接)

本次開源項(xiàng)目為愛鮮蜂,一款電商APP,使用語言Swift2.0,開發(fā)工具Xcode7.0.1.

項(xiàng)目為純代碼開發(fā),沒有使用XIB和StoryBoard.開發(fā)周期大概為2個(gè)月左右(工作閑暇之余).

數(shù)據(jù)都是本地?cái)?shù)據(jù),輔助開發(fā)軟件:PhotoShop CS6(圖片處理),Charles(抓包工具).

寫的比較匆忙,很多地方無法盡善盡美,如果有建議和可優(yōu)化的地方可在文章底部留言,我會一一查看的并回復(fù)的.

項(xiàng)目效果圖

效果圖1
效果圖2
效果圖3
效果圖4
效果圖5
效果圖6
效果圖7
效果圖8
效果圖9

項(xiàng)目詳細(xì)講解(根據(jù)啟動流程)

引導(dǎo)頁和AD(廣告)頁

當(dāng)程序被打開時(shí),在創(chuàng)建KeyWindow的RootViewController時(shí)判斷是否是首次登陸,這里的邏輯是如果用戶是首次打開應(yīng)用的話顯示引導(dǎo)頁,當(dāng)點(diǎn)擊引導(dǎo)頁最后一頁的立即體驗(yàn)直接進(jìn)入TabBarController,不顯示廣告頁(效果如下圖)

引導(dǎo)頁

如果用戶不是首次打開應(yīng)用的話,則顯示廣告頁,并且在4秒后以放大并且透明的效果進(jìn)入TabBarController(效果如下圖)

廣告頁

邏輯代碼如下

    // 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()
        }
    }

引導(dǎo)頁考慮到循環(huán)利用,使用的是UICollectionView實(shí)現(xiàn)

AD(廣告)頁需要注意的是ADViewController有時(shí)會存在加載廣告圖片失敗的情況,如果加載失敗,發(fā)送加載圖片失敗的通知,window直接將tarBarController作為keyWindow即可

關(guān)于引導(dǎo)頁和AD頁的實(shí)現(xiàn)代碼,我就不詳細(xì)講述了,源代碼都有,有興趣的讀者可以打開代碼自行研究

AnimationTabBarController(帶有動畫的TabBarItem)

這里有個(gè)小故事,我是無意中發(fā)現(xiàn)愛鮮蜂底部TabBarItem有點(diǎn)擊的動畫,感覺挺有意思的,嘗試著自己實(shí)現(xiàn)了同樣的功能,然后才突發(fā)奇將后續(xù)的功能都給實(shí)現(xiàn)了.先看下底部UITabBarItem的動畫(效果如下圖)

TaBarItem動畫效果

底部的TabBarItem動畫使用了三方框架RAMAnimatedTabBar,由于原來的框架 只能通過StoryBoard初始化控件,并且無法滿足項(xiàng)目需求,所以對框架進(jìn)行了大量修改,其原理很簡單,就是在TabBarController初始化時(shí),通過攔截Items,重新創(chuàng)建一套相同的View,并且在每個(gè)View上添加ImageView和Label,在View的點(diǎn)擊事件中,控制動畫即可.用這種方式也可以輕松完成一些看似復(fù)雜的動畫,如下圖所示,其實(shí)通過ImageView的序列動畫就可以輕松完成,只是需要在AE中做出動畫序列圖即可.

通過序列動畫達(dá)到的效果

首頁

首頁由三部分構(gòu)成,頂部的輪播圖(PageScrollView),輪播圖下面的活動按鈕,以及UICollectionView.(如下圖所示)

首頁效果圖

其中將PageScrollView與活動按鈕封裝成了UICollectionView的headView,通過代理方法將點(diǎn)擊的事件,需要注意的是活動按鈕并不是固定只有四個(gè),這里是根據(jù)服務(wù)器返回的數(shù)據(jù)創(chuàng)建的,有時(shí)候會有多個(gè),需要根據(jù)數(shù)量控制列數(shù),通過代理方法將高度傳給首頁的控制器.

PageScrollView(輪播圖)

這里采用循環(huán)利用機(jī)制寫的,只創(chuàng)建3個(gè)ImageView即可,在同一時(shí)刻屏幕中最多只會顯示2個(gè)ImageView,當(dāng)需要展示新的ImageView,只需要將緩存數(shù)組中的沒有展示的ImageView拿出來展示即可,這里為了方便大家將項(xiàng)目移植到自己的項(xiàng)目中使用,我將代碼全部拷貝過來了,需要替換數(shù)據(jù)修改headData內(nèi)的數(shù)據(jù)

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: 更新內(nèi)容
    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展示對應(yīng)Cell即可.

新Cell出現(xiàn)的??縿赢?如圖

??縿赢?/div>

通過實(shí)現(xiàn)UICollectionViewDelegate,在willDisplayCell代理方法完成動畫,代碼如下

    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
        })
    }

添加商品動畫

當(dāng)用戶點(diǎn)擊加號時(shí),會出現(xiàn)如下如所示動畫

添加商品到購物車動畫效果

添加商品到購物車基于CoreAnimation(核心動畫)實(shí)現(xiàn),通過對ImageView的layer添加縮放,透明度以及路徑動畫實(shí)現(xiàn).代碼如下

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個(gè)TableView構(gòu)成,如圖所示

閃電超市效果圖

這里采用了2個(gè)控制器分別管理各自的TableView,將TableView2添加到VC2上,將VC2.view添加到VC1.view上,然后再通過VC1.addChildViewController將VC2添加到VC1的子控制器中,這樣既降低了代碼的復(fù)雜性,有提升了代碼維護(hù)性,各自管理各自的TableView.

很多聯(lián)動的操作都是通過UITableViewDelegate中實(shí)現(xiàn)的,有興趣的同學(xué)可參照代碼自行研究.

購物車

購物車采用了modal的形式出現(xiàn).樣式上有兩種情況,當(dāng)購物車?yán)餂]有商品時(shí),購物車顯示為空(如下圖)

當(dāng)購物車為空

當(dāng)購物車中有商品時(shí)候,顯示商品信息(如下圖)

有商品時(shí)的購物車

這里封裝了一個(gè)UserShopCar單利類,專門用來管理用戶購物車,保存用戶添加到購物車的商品種類,商品總數(shù),商品價(jià)格等,在購物車VC將要出現(xiàn)時(shí)候,判斷購物車是否為空,如果為空則顯示去逛逛,如果不為空則顯示商品的信息.內(nèi)部細(xì)節(jié)請參考代碼

購物車上紅色圓圈

購物車紅色圓圈也是通過單利類來實(shí)現(xiàn)的,內(nèi)部提供倆個(gè)方法,添加商品和移除商品,方法內(nèi)部包含動畫效果,當(dāng)添加商品或者減掉商品時(shí),調(diào)用對象對應(yīng)的方法即可.

我的

先看下效果

我的效果圖

我的頁面是項(xiàng)目中最為復(fù)雜的頁面,包含了許多效果.我大體講一下思路,我的頁面是由頂部的View以及一個(gè)TableView構(gòu)成,TableView有一個(gè)headView,分別是我的訂單,優(yōu)惠劵以及我的消息,通過閉包的回調(diào)完成點(diǎn)擊的事件.這個(gè)頁面比較簡單,不過多敘述了.

右上角設(shè)置按鈕

設(shè)置效果圖

設(shè)置頁面沒有使用TableView,單純的用View搭建的.

清理緩存這稍微說一下吧,同樣也封裝了一個(gè)工具類,提供四個(gè)方法,分別是參看單個(gè)文件的大小,查看全部文件大小文件大小,同步將文件夾清除以及異步清除文件夾.有需要的同學(xué)可以直接copy走,那去使用,path是文件的路徑

import UIKit

class FileTool: NSObject {
    
    static let fileManager = NSFileManager.defaultManager()
    
    /// 計(jì)算單個(gè)文件的大小
    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
    }
    
    /// 計(jì)算整個(gè)文件夾的大小
    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個(gè)控制器構(gòu)成,分別是MyOrderViewController(我的訂單)和OrderViewDetailViewController(訂單詳情)

我的訂單效果
我的訂單

一個(gè)TableView搞定,cell的樣式也并不復(fù)雜,這里有個(gè)小細(xì)節(jié)就是需要判斷訂單商品種類的個(gè)數(shù),如果是4個(gè)以上,只顯示五張圖片,并且第五張圖片以...圖片顯示,在設(shè)置cell的model時(shí),判斷商品種類個(gè)數(shù)即可實(shí)現(xiàn).發(fā)福利等按鈕是根據(jù)服務(wù)器返回的數(shù)據(jù)創(chuàng)建的,不同類型的Button會有不同的Type,type的值為int類型的,這里可以將button的tag設(shè)置對應(yīng)的typr,這點(diǎn)擊的時(shí)候判斷button的tag,通過Swith語法執(zhí)行對應(yīng)的操作就可以了.

訂單詳情頁

訂單詳情頁有兩部分,分別是訂單狀態(tài)以及訂單詳情,通過導(dǎo)航欄的titleView(也就是UISegmentedControl)來切換顯示不同界面.

訂單詳情頁也是一個(gè)TableView就可以搞定,服務(wù)器返回是一個(gè)數(shù)組,這里的邏輯是,當(dāng)前狀態(tài)是0時(shí),圓形圖片為黃色,并且沒有上面的線,最下面的狀態(tài)沒有下邊的線,這里的做法是給cell的model賦值的時(shí)候,將indexPath一同傳入給Cell,判斷indexPath.row是多少,如果是0,就將圓形圖片顯示為黃色,并且隱藏上半部分線,同理當(dāng)indexPath.row等于狀態(tài)數(shù)組的count-1時(shí),隱藏下半部分的線即可搞定.

訂單詳情也是通過TableView實(shí)現(xiàn)的,采用tableView是考慮到商品種類的cell可以循環(huán)利用,頂部的訂單細(xì)信息和收貨人地址為TableHeadView(效果如下圖)

訂單詳情結(jié)構(gòu)

底部評價(jià)為tableFootView(效果如下圖)

訂單詳情結(jié)構(gòu)

優(yōu)惠劵

優(yōu)惠劵效果圖

優(yōu)惠劵也是由TableView構(gòu)成的,有兩種cell,一種是可以使用的優(yōu)惠劵,另一種為不可使用的優(yōu)惠劵,這里通過模型判斷cell的展示的類型.

使用規(guī)則為H5頁面,直接在webView上loadURL就OK了.

我的消息

我的消息效果圖

依然是tableView,不過這里會根據(jù)用戶操作動態(tài)改變cell的高度.做法是在給cell設(shè)置模型的同時(shí),計(jì)算出cell全部展示的高度,在模型中創(chuàng)建輔助參數(shù),保存cell的真實(shí)高度以及未打開時(shí)的高度,在UITableViewDelegate獲取cell的高度時(shí),判斷當(dāng)前cell的狀態(tài),根據(jù)狀態(tài)返回對應(yīng)的高度.

cell內(nèi)部的顯示全部按鈕通過閉包回調(diào)告訴控制器點(diǎn)擊事件,同時(shí)將cell的IndexPath作為參數(shù)傳出來,當(dāng)用戶點(diǎn)擊顯示全部時(shí),根據(jù)當(dāng)前cell的狀態(tài)取反,同時(shí)tableView.reloadData就動態(tài)的改變cell的高度了.

我的收貨地址

我的收貨地址效果圖

額,還是tableView,不說了.

編輯我的地址有兩種情況,一種是修改現(xiàn)有的收貨地址,進(jìn)入時(shí)對應(yīng)的選項(xiàng)都已經(jīng)存在,并且有刪除當(dāng)前地址的View在底部.另外一種是添加新地址,沒有參數(shù)和刪除當(dāng)前地址view,這里在EditAdressViewController寫一個(gè)枚舉,并且搞一個(gè)成員變量type

enum EditAdressViewControllerType: Int {
    case Add
    case Edit
}

在push進(jìn)入EditAdressViewController時(shí),將EditAdressViewController的類型傳入,根據(jù)type的類型,顯示對應(yīng)的數(shù)據(jù)即可.

常見問題

效果如下圖:

常見問題效果圖

常見問題這里UI的搭建相信讀者都了然于心,不過多介紹,只講一下點(diǎn)擊出現(xiàn)動畫的邏輯.這里也是一個(gè)TableView,常見問題為tableView的headView,詳細(xì)問題View為tableView的Cell,默認(rèn)cell的個(gè)數(shù)為零,當(dāng)用戶點(diǎn)擊了headView,記錄點(diǎn)擊的headView的indexPath.section,刷新tableView,將點(diǎn)擊行的Cell個(gè)數(shù)返回為1,并且單獨(dú)給這一行的cell返回cell的高度.代碼如下

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,需要在真機(jī)上才可以分享,不過Sina微博需要在后臺配置測試賬號,大家可能無法測試新浪微博分享~,關(guān)于分享也是封裝了ShareManager工具類,定義好分享枚舉類型

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

將彈出的樣式也ActionSheet也封裝成單個(gè)類

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
            }
        }
    }
    
}

當(dāng)外部需要調(diào)用分享的時(shí)候,只需要調(diào)用一句代碼即可

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

掃一掃

掃一掃效果圖

注意需要在真機(jī)上才可以測試,模擬器沒有攝像頭的.這里用的iOS7.0以后蘋果自帶框架AVFoundation,使用非常簡單,這也就不過多敘述,講一下如何實(shí)現(xiàn)中間區(qū)域亮,四邊為黑色的效果,其實(shí)原理很簡單,在view上創(chuàng)建四個(gè)view,如下圖

將View的背景色改為黑色,透明度為0.5,添加到View上,搞定.
設(shè)置captureMetadataOutput.rectOfInterest的范圍,控制掃描區(qū)域的敏感范圍.

搜索控制器

效果如下

搜索效果圖

搜索控制器導(dǎo)航欄上的搜索條使用的UISearchBar,下面為按鈕,需要?jiǎng)討B(tài)的布局按鈕的位置,這里有熱搜索和歷史搜索,考慮到復(fù)用性,將搜索View封裝成一個(gè)View,在便利構(gòu)造方法中將按鈕的名字?jǐn)?shù)組傳入,自定在內(nèi)部布局,計(jì)算高度,通過閉包回調(diào)將按鈕點(diǎn)擊的事件通知給控制器,具體代碼如下


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..<searchButtonTitleTexts.count {
            let btn = UIButton()
            btn.setTitle(searchButtonTitleTexts[i], forState: UIControlState.Normal)
            btn.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
            btn.titleLabel?.font = UIFont.systemFontOfSize(14)
            btn.titleLabel?.sizeToFit()
            btn.backgroundColor = UIColor.whiteColor()
            btn.layer.masksToBounds = true
            btn.layer.cornerRadius = 15
            btn.layer.borderWidth = 0.5
            btn.layer.borderColor = UIColor.colorWithCustom(200, g: 200, b: 200).CGColor
            btn.addTarget(self, action: "searchButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
            btnW = btn.titleLabel!.width + addW
            
            if frame.width - lastX > 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)
        }
    }
}

嘮叨一下

關(guān)于項(xiàng)目的內(nèi)容,并不是短短幾千文字就能給大家講明白的,想要了解更多內(nèi)容,請打開代碼仔細(xì)研究,小熊還是抱著為了大家能看懂的套路基本沒有使用三方框架,應(yīng)小東同學(xué)的要求使用了純代碼開發(fā).希望對大家有所幫助.記著點(diǎn)個(gè)Star哈~

最近有點(diǎn)忙,加上整個(gè)人回到北京好像變懶了T_T,有段日子沒有發(fā)布什么資源了.這個(gè)項(xiàng)目就作為新年禮物送給大家吧,還有兩個(gè)小時(shí),開網(wǎng)小熊回家的列車就出發(fā)了,在這里提前祝大家新年快樂~

代碼下載地址

代碼下載地址,歡迎點(diǎn)贊和反饋

小熊的技術(shù)博客

點(diǎn)擊鏈接我的博客

小熊的新浪微博

我的新浪微博

本文為作者原著,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,631評論 4 61
  • 一 初露鋒芒 不知道什么有了這么一個(gè)傳說,據(jù)說是那上古時(shí)候,邪獸淫蟲占據(jù)著山林,猛蛟巨鰲馳騁在水面,人類的部族被驅(qū)...
    月下破曉閱讀 412評論 0 1
  • 可用性設(shè)計(jì)就是以提高產(chǎn)品的可用性為核心的設(shè)計(jì),它是設(shè)計(jì)藝術(shù)心理學(xué)運(yùn)用于設(shè)計(jì)踐中,指導(dǎo)設(shè)計(jì)的一個(gè)重要組成部分。可用性...
    ae627d4a1271閱讀 1,781評論 1 4
  • 聽說明天會下雪,也不知道是不是真的,好生硬的開頭,其實(shí)事情更生硬… 文章的主要目的是說關(guān)于尋鮮家一道菜,也...
    尋鮮辣廚閱讀 415評論 0 0

友情鏈接更多精彩內(nèi)容