iOS高仿愛鮮蜂

MikYWF 8年前發布 | 38K 次閱讀 UIKit iOS開發 移動開發

來自: http://www.jianshu.com/p/879f58fe3542

iOS高仿愛鮮蜂

前言

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

關于項目(代碼下載地址在文章最下面GitHub鏈接)

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

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

數據都是本地數據,輔助開發軟件:PhotoShop CS6(圖片處理),Charles(抓包工具).

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

項目效果圖

效果圖1

</div>

效果圖2

</div>

效果圖3

</div>

效果圖4

</div>

效果圖5

</div>

效果圖6

</div>

效果圖7

</div>

效果圖8

</div>

效果圖9

</div>

項目詳細講解(根據啟動流程)

引導頁和AD(廣告)頁

當程序被打開時,在創建KeyWindow的RootViewController時判斷是否是首次登陸,這里的邏輯是如果用戶是首次打開應用的話顯示引導頁,當點擊引導頁最后一頁的 立即體驗 直接進入TabBarController,不顯示廣告頁(效果如下圖)

引導頁

</div>

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

廣告頁

</div>

邏輯代碼如下

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

引導頁考慮到循環利用,使用的是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.(如下圖所示)

首頁效果圖

</div>

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

PageScrollView(輪播圖)

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

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

}</pre>

首頁UICollectionView

首頁的CollectionView采用了兩種Cell,一種是只有ImageView的Cell,一種是商品的Cell(如圖所示)

首頁Cell樣式一

</div>

首頁Cell樣式二

</div>

通過判斷indexPath.section展示對應Cell即可.

新Cell出現的停靠動畫,如圖

停靠動畫

</div>

通過實現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
    })
}</pre> 

添加商品動畫

當用戶點擊加號時,會出現如下如所示動畫

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

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

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

}</pre>

閃電超市

閃電超市很明顯由有2個TableView構成,如圖所示

閃電超市效果圖

</div>

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

很多聯動的操作都是通過UITableViewDelegate中實現的,有興趣的同學可參照代碼自行研究.

購物車

購物車采用了modal的形式出現.樣式上有兩種情況,當購物車里沒有商品時,購物車顯示為空(如下圖)

當購物車為空

</div>

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

有商品時的購物車

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

購物車上紅色圓圈

購物車紅色圓圈也是通過單利類來實現的,內部提供倆個方法,添加商品和移除商品,方法內部包含動畫效果,當添加商品或者減掉商品時,調用對象對應的方法即可.

我的

先看下效果

我的效果圖

</div>

我的頁面是項目中最為復雜的頁面,包含了許多效果.我大體講一下思路,我的頁面是由頂部的View以及一個TableView構成,TableView有一個headView,分別是我的訂單,優惠劵以及我的消息,通過閉包的回調完成點擊的事件.這個頁面比較簡單,不過多敘述了.

右上角設置按鈕

設置效果圖

</div>

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

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

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

}</pre>

我的訂單

我的訂單由2個控制器構成,分別是MyOrderViewController(我的訂單)和OrderViewDetailViewController(訂單詳情)

我的訂單效果

</div>

我的訂單

一個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(效果如下圖)

訂單詳情結構

</div>

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

訂單詳情結構

</div>

優惠劵

優惠劵效果圖

</div>

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

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

我的消息

我的消息效果圖

</div>

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

cell內部的顯示全部按鈕通過閉包回調告訴控制器點擊事件,同時將cell的IndexPath作為參數傳出來,當用戶點擊顯示全部時,根據當前cell的狀態取反,同時tableView.reloadData就動態的改變cell的高度了.

我的收貨地址

我的收貨地址效果圖

額,還是tableView,不說了.

編輯我的地址有兩種情況,一種是修改現有的收貨地址,進入時對應的選項都已經存在,并且有刪除當前地址的View在底部.另外一種是添加新地址,沒有參數和刪除當前地址view,這里在EditAdressViewController寫一個枚舉,并且搞一個成員變量type

enum EditAdressViewControllerType: Int {
    case Add
    case Edit
}

在push進入EditAdressViewController時,將EditAdressViewController的類型傳入,根據type的類型,顯示對應的數據即可.

常見問題

效果如下圖:

常見問題效果圖

</div>

常見問題這里UI的搭建相信讀者都了然于心,不過多介紹,只講一下點擊出現動畫的邏輯.這里也是一個TableView,常見問題為tableView的headView,詳細問題View為tableView的Cell,默認cell的個數為零,當用戶點擊了headView,記錄點擊的headView的indexPath.section,刷新tableView,將點擊行的Cell個數返回為1,并且單獨給這一行的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)
}

}</pre>

其他功能

三方分享分享

這里我使用了友盟分享SDK,需要在真機上才可以分享,不過Sina微博需要在后臺配置測試賬號,大家可能無法測試新浪微博分享~,關于分享也是封裝了ShareManager工具類,定義好分享枚舉類型

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

將彈出的樣式也ActionSheet也封裝成單個類

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

}</pre>

當外部需要調用分享的時候,只需要調用一句代碼即可

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

掃一掃

掃一掃效果圖

</div>

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

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

搜索控制器

效果如下

搜索效果圖

</div>

搜索控制器導航欄上的搜索條使用的UISearchBar,下面為按鈕,需要動態的布局按鈕的位置,這里有熱搜索和歷史搜索,考慮到復用性,將搜索View封裝成一個View,在便利構造方法中將按鈕的名字數組傳入,自定在內部布局,計算高度,通過閉包回調將按鈕點擊的事件通知給控制器,具體代碼如下

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

}</pre>

嘮叨一下

關于項目的內容,并不是短短幾千文字就能給大家講明白的,想要了解更多內容,請打開代碼仔細研究,小熊還是抱著為了大家能看懂的套路基本沒有使用三方框架,應小東同學的要求使用了純代碼開發.希望對大家有所幫助.記著點個Star哈~

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

代碼下載地址

代碼下載地址,歡迎點贊和反饋

小熊的技術博客

點擊鏈接我的博客

小熊的新浪微博

我的新浪微博

本文為作者原著,歡迎轉載,轉載請注明作者出處

</div>

 本文由用戶 MikYWF 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!