iOS高仿:花田小憩3.0.1

来源:http://www.cnblogs.com/coderAlin/archive/2016/06/09/5572372.html
-Advertisement-
Play Games

前言 斷斷續續的已經學習 一年多了, 從 到現在的 , 一直在語法之間徘徊, 學一段時間, 工作一忙, 再撿起來隔段時間又忘了.思來想去, 趁著這兩個月加班不是特別多, 就決定用 仿寫一個完整項目. 花田小憩:是一個植物美學生活平臺, 以自然生活為主導, 提倡植物學生活方法, 倡導美學標準的生活態度 ...


前言

斷斷續續的已經學習Swift一年多了, 從1.2到現在的2.1, 一直在語法之間徘徊, 學一段時間, 工作一忙, 再撿起來隔段時間又忘了.思來想去, 趁著這兩個月加班不是特別多, 就決定用swift仿寫一個完整項目.

花田小憩:是一個植物美學生活平臺,
以自然生活為主導,
提倡植物學生活方法,
倡導美學標準的生活態度的一個APP.

個人文字功底有限, 就我而言, 這款APP做的挺唯美的...

聲明

花田小憩項目裡面的都是真實介面, 真實數據, 僅供學習, 毋作其他用途!!!

TIP: 如果截圖顯示不全的話, 可以訪問下麵的鏈接

簡書

項目部分截圖

由於項目的大體功能都已經實現了的, 所以整個項目還是比較龐大的.所以, 下麵羅列部分功能的截圖.
由於gif錄製的時候, 會重新渲染一遍圖片, 所以導致項目中用到高斯模糊的地方, 看起來感覺比較亂, 實際效果還是不錯的.

新特性

首頁

詳情頁

分享

評論

商城首頁

商城詳情頁

商品搜索

支付

圖片瀏覽器

專欄作家

個人中心

項目環境

編譯器 : Xcode7.2

語言 : Swift2.1

整個項目都是採用純代碼開發模式

第三方框架

use_frameworks!
platform :ios, "8.0"

target 'Floral' do

pod 'SnapKit', '~> 0.20.0' ## 自動佈局
pod 'Alamofire', '~> 3.3.1' ## 網路請求, swift版的AFN
pod 'Kingfisher', '~> 2.3.1' ## 輕量級的SDWebImage

end

還用到了MBProgressHUD.
除此之外,幾乎全部都是自己造的小輪子...

目錄結構詳解

Classes下包含7個功能目錄:

Resources : 項目用到的資源,包含plist文件, js文件字體

Network : 網路請求, 所有的網路請求都在這裡面, 介面參數都有詳細的註釋

Tool : 包含tools(工具類), 3rdLib(第三方:友盟分享, MBProgressHUD ), Category(所有項目用到的分類)

Home : 首頁(專題), 包含專題分類, 詳情, 每周Top10, 評論, 分享等等功能模塊

Main : UITabBarController, UINavigationController設置以及新特性

Malls : 商城, 包含商城分類, 商品搜索, 詳情, 購物車, 購買, 訂單, 地址管理, 支付等等功能模塊

Profile : 個人中心, 專欄作者, 登錄/註冊/忘記密碼, 設置等功能模塊

大家可以下載項目, 對照這個目錄結構進行查看, 很典型的MVC文件結構, 還是很方便的.

項目部分功能模塊詳解

① 新特性NewFeatureViewController : 這個功能模塊還是比較簡單的, 用到了UICollectionViewController, 然後自己添加了UIPageControl, 只需要監聽最後一個cell的點擊即可.

這兒有一個註意點是: 我們需要根據版本號來判斷是進入新特性界面, 廣告頁還是首頁.

  private let SLBundleShortVersionString = "SLBundleShortVersionString"
    // MARK: - 判斷版本號
  private func toNewFeature() -> Bool
    {
        // 根據版本號來確定是否進入新特性界面
        let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String
        let oldVersion = NSUserDefaults.standardUserDefaults().objectForKey(SLBundleShortVersionString) ?? ""
        
        // 如果當前的版本號和本地保存的版本比較是降序, 則需要顯示新特性
        if (currentVersion.compare(oldVersion as! String)) == .OrderedDescending{
            // 保存當前的版本
             NSUserDefaults.standardUserDefaults().setObject(currentVersion, forKey: SLBundleShortVersionString)
            return true
        }
        return false
    }

② 下拉刷新RefreshControl : 在這個項目中, 沒有用第三方的下拉刷新控制項, 而是自己實現了一個簡單的下拉刷新輪子, 然後賦值給UITableViewControllerpublic var refreshControl: UIRefreshControl?屬性. 主要原理就是判斷下拉時的frame變化:

// 監聽frame的變化
        addObserver(self, forKeyPath: "frame", options:.New, context: nil)
 // 刷新的時候, 不再進行其他操作
    private var isLoading = false
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let y = frame.origin.y
        // 1. 最開始一進來的時候, 刷新按鈕是隱藏的, y就是-64, 需要先判斷掉, y>=0 , 說明刷新控制項已經完全縮回去了...
        if y >= 0 || y == -64
        {
            return
        }
        
        // 2. 判斷是否一進來就進行刷新
        if beginAnimFlag && (y == -60.0 || y == -124.0){
            if !isLoading {
                isLoading = true
                animtoringFlag = true
                tipView.beginLoadingAnimator()
            }
            return
        }
     
        // 3. 釋放已經觸發了刷新事件, 如果觸發了, 需要進行旋轉
        if refreshing && !animtoringFlag
        {
            animtoringFlag = true
            tipView.beginLoadingAnimator()
            return
        }
        
        if y <= -50 && !rotationFlag
        {
            rotationFlag = true
            tipView.rotationRefresh(rotationFlag)
        }else if(y > -50 && rotationFlag){
            rotationFlag = false
            tipView.rotationRefresh(rotationFlag)
        }
    }

高斯模糊: 使用的是系統自帶的高斯模糊控制項UIVisualEffectView, 它是@available(iOS 8.0, *), 附一段簡單的使用代碼

private lazy var blurView : BlurView = {
        let blur = BlurView(effect: UIBlurEffect(style: .Light))
        blur.categories = self.categories
        blur.delegate = self
        return blur
    }()

可以根據alpha = 0.5, 調整alpha來調整模糊效果, gif圖中的高斯模糊效果不是很明顯, 實際效果特別好.

高斯模糊

商城購物車動畫:這組動畫還是比較簡單的, 直接附代碼, 如果有什麼疑惑, 可以留言或者私信我

// MARK : - 動畫相關懶載入
    /// layer
    private lazy var animLayer : CALayer = {
        let layer = CALayer()
        layer.contentsGravity = kCAGravityResizeAspectFill;
        layer.bounds = CGRectMake(0, 0, 50, 50);
        layer.cornerRadius = CGRectGetHeight(layer.bounds) / 2
        layer.masksToBounds = true;
        return layer
    }()
    
    /// 貝塞爾路徑
    private lazy var animPath = UIBezierPath()
    
    /// 動畫組
    private lazy var groupAnim : CAAnimationGroup = {
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.path = self.animPath.CGPath
        animation.rotationMode = kCAAnimationRotateAuto
        
        let expandAnimation = CABasicAnimation(keyPath: "transform.scale")
        expandAnimation.duration = 1
        expandAnimation.fromValue = 0.5
        expandAnimation.toValue = 2
        expandAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
        
        let narrowAnimation = CABasicAnimation(keyPath: "transform.scale")
        // 先執行上面的, 然後再開始
        narrowAnimation.beginTime = 1
        narrowAnimation.duration = 0.5
        narrowAnimation.fromValue = 2
        narrowAnimation.toValue = 0.5
        narrowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        
        let groups = CAAnimationGroup()
        groups.animations = [animation,expandAnimation,narrowAnimation]
        groups.duration = 1.5
        groups.removedOnCompletion = false
        groups.fillMode = kCAFillModeForwards
        groups.delegate = self
        return groups
    }()
    
    
    // MARK: - 點擊事件處理
    private var num = 0
    func gotoShopCar() {
        if num >= 99 {
            self.showErrorMessage("親, 企業採購請聯繫我們客服")
            return
        }
        addtoCar.userInteractionEnabled = false
        
        // 設置layer
        // 貝塞爾弧線的起點
        animLayer.position = addtoCar.center
        layer.addSublayer(animLayer)
        // 設置path
        animPath.moveToPoint(animLayer.position)
        
        let controlPointX = CGRectGetMaxX(addtoCar.frame) * 0.5
        
        // 弧線, controlPoint基準點, endPoint結束點
        animPath.addQuadCurveToPoint(shopCarBtn.center, controlPoint: CGPointMake(controlPointX, -frame.size.height * 5))
        
        // 添加並開始動畫
        animLayer.addAnimation(groupAnim, forKey: "groups")
    }
    
    // MARK: - 動畫的代理
    // 動畫停止的代理
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if anim ==  animLayer.animationForKey("groups")!{
            animLayer.removeFromSuperlayer()
            animLayer.removeAllAnimations()
            
            num += 1
            shopCarBtn.num = num
            
            let animation = CATransition()
            animation.duration = 0.25
            
            shopCarBtn.layer.addAnimation(animation, forKey: nil)
            
            let shakeAnimation = CABasicAnimation(keyPath: "transform.translation.y")
            shakeAnimation.duration = 0.25
            shakeAnimation.fromValue = -5
            shakeAnimation.toValue = 5
            shakeAnimation.autoreverses = true
            
            shopCarBtn.layer .addAnimation(shakeAnimation, forKey: nil)
            addtoCar.userInteractionEnabled = true

        }
    }

⑤ 主題詳情頁:商城詳情頁的做法也是差不多的, 不過更簡單一點.

主題詳情頁

關鍵一點在於, 詳情頁的展示主要依靠於H5頁面. 而我們需要根據webview的高度來確定webviewCell的高度.我的做法是監聽UIWebViewwebViewDidFinishLoad, 取出webView.scrollView.contentSize.height然後給詳情頁發送一個通知, 讓其刷新界面. 暫時沒有想到更好的方法, 如果您有更好的做法, 請務必告訴我, 謝謝...

UIWebView中圖片的點擊

第①步: 我們創建一個image.js文件, 代碼如下:

//setImage的作用是為頁面的中img元素添加onClick事件,即設置點擊時調用imageClick
function setImageClick(){
    var imgs = document.getElementsByTagName("img");
    for (var i=0;i<imgs.length;i++){
        var src = imgs[i].src;
        imgs[i].setAttribute("onClick","imageClick(src)");
    }
    document.location = imageurls;
}

//imageClick即圖片 onClick時觸發的方法,document.location = url;的作用是使調用
//webView: shouldStartLoadWithRequest: navigationType:方法,在該方法中我們真正處理圖片的點擊
function imageClick(imagesrc){
    var url="imageClick::"+imagesrc;
    document.location = url;
}

第②步:在UIWebView的代理方法webViewDidFinishLoad中, 載入JS文件, 並給圖片綁定綁定點擊事件

// 載入js文件
        webView.stringByEvaluatingJavaScriptFromString(try! String(contentsOfURL: NSBundle.mainBundle().URLForResource("image", withExtension: "js")!, encoding: NSUTF8StringEncoding))
        
        // 給圖片綁定點擊事件
        webView.stringByEvaluatingJavaScriptFromString("setImageClick()")

第③步:在UIWebView的代理方法-webView:shouldStartLoadWithRequest:navigationType:中判斷圖片的點擊

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        let urlstr = request.URL?.absoluteString
        let components : [String] = urlstr!.componentsSeparatedByString("::")
        if (components.count >= 1) {
            //判斷是不是圖片點擊
            if (components[0] == "imageclick") {
                parentViewController?.presentViewController(ImageBrowserViewController(urls: [NSURL(string: components.last!)!], index: NSIndexPath(forItem: 0, inSection: 0)), animated: true, completion: nil)
                return false;
            }
            return true;
        }
        return true
    }

⑦ 登錄/註冊/忘記密碼: 眼尖一點的朋友可能在上面的gif中已經發現, 花田小憩中的登錄/註冊/忘記密碼界面幾乎是一樣的, 我的做法是用一個控制器LoginViewController來代表登錄/註冊/忘記密碼三個功能模塊, 通過兩個變數isRegisterisRevPwd來判斷是哪個功能, 顯示哪些界面, 我們點擊註冊忘記密碼的時候, 會執行代理方法:

// MARK: - LoginHeaderViewDelegate
    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRevpwd pwdBtn: UIButton) {
        let login = LoginViewController()
        login.isRevPwd = true
        navigationController?.pushViewController(login, animated: true)
    }
    
    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRegister registerbtn: UIButton) {
        let login = LoginViewController()
        login.isRegister = true
        navigationController?.pushViewController(login, animated: true)
    }

⑧ 驗證碼的倒計時功能

驗證碼倒計時

/// 點擊"發送驗證碼"按鈕
    func clickSafeNum(btn: UIButton) {
        var seconds = 10 //倒計時時間
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
        dispatch_source_set_timer(timer,dispatch_walltime(nil, 0),1 * NSEC_PER_SEC, 0); //每秒執行
        dispatch_source_set_event_handler(timer) { 
            if(seconds<=0){ //倒計時結束,關閉
                dispatch_source_cancel(timer);
                dispatch_async(dispatch_get_main_queue(), {
                    //設置界面的按鈕顯示 根據自己需求設置
                    btn.setTitleColor(UIColor.blackColor(), forState:.Normal)
                    btn.setTitle("獲取驗證碼", forState:.Normal)
                    btn.titleLabel?.font = defaultFont14
                    btn.userInteractionEnabled = true
                    });
            }else{
                
                dispatch_async(dispatch_get_main_queue(), {
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                })
                dispatch_async(dispatch_get_main_queue(), {
                    //設置界面的按鈕顯示 根據自己需求設置
                    UIView.beginAnimations(nil, context: nil)
                    UIView.setAnimationDuration(1)
                    btn.setTitleColor(UIColor.orangeColor(), forState:.Normal)
                    btn.setTitle("\(seconds)秒後重新發送", forState:.Normal)
                    btn.titleLabel?.font = UIFont.systemFontOfSize(11)
                    UIView.commitAnimations()
                    btn.userInteractionEnabled = false
                
                })
               seconds -= 1

        }
            
    }
    dispatch_resume(timer)
}

設置模塊中給我們評分

這個功能在實際開發中特別常見:

給我們評分

代碼如下, 很簡單:

UIApplication.sharedApplication().openURL(NSURL(string: "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=998252000")!)

其中最後的id需要填寫你自己的APP在AppStore中的id, 打開iTunes找到你自己的APP或者你想要的APP, 就能查看到id.

tip: 此功能測試的時候, 必須用真機!!!

⑩ 登錄狀態.

我們可以通過NSHTTPCookieStorage中的NSHTTPCookie來判斷登錄狀態.也可以自定義一個欄位來保存. 根據我抓包得知, 花田小憩APP的做法是第一次登錄後保存用戶名和密碼(MD5加密的, 我測試過), 然後每次啟動應用程式的時候, 會首先後臺自動登錄, 然後在進行評論/點贊等操作的時候呢, 參數中會帶上用戶的id.由於涉及到花田小憩的賬號密碼的一些隱私, 所以登錄/註冊模塊, 我就沒有沒有完整的寫出來. 有興趣的朋友可以私信我, 我可以把介面給你, 在此聲明: 僅供學習, 毋做傷天害理之事

`tip: 我在AppDelegate.swift中給大家留了一個開關, 可以快速的進行登錄狀態的切換...

⑩+①: 個人/專欄中心: 這兩個功能是同一個控制器, 是UICollectionViewController而不是UITableViewController

個人中心

大家對UITableViewControllerheader應該很熟悉吧, 向上滑動的時候, 會停留在navigationBar的下麵, 雖然UICollectionViewController也可以設置header, 但是在iOS9以前, 他是不能直接設置停留的.在iOS9之後, 可以一行代碼設置header的停留

sectionHeadersPinToVisibleBounds = true

但是在iOS9之前, 我們需要自己實現這個功能:

//
//  LevitateHeaderFlowLayout.swift
//  Floral
//
//  Created by ALin on 16/5/20.
//  Copyright © 2016年 ALin. All rights reserved.
//  可以讓header懸浮的流水佈局

import UIKit

class LevitateHeaderFlowLayout: UICollectionViewFlowLayout {
    override func prepareLayout() {
        super.prepareLayout()
        // 即使界面內容沒有超過界面大小,也要豎直方向滑動
        collectionView?.alwaysBounceVertical = true
        // sectionHeader停留
        if #available(iOS 9.0, *) {
            sectionHeadersPinToVisibleBounds = true
        }
    }
    
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        // 1. 獲取父類返回的UICollectionViewLayoutAttributes數組
        var answer = super.layoutAttributesForElementsInRect(rect)!
        
        // 2. 如果是iOS9.0以上, 直接返回父類的即可. 不用執行下麵的操作了. 因為我們直接設置sectionHeadersPinToVisibleBounds = true即可
        if #available(iOS 9.0, *) {
            return answer
        }
        
        // 3. 如果是iOS9.0以下的系統
        
        // 以下代碼來源:http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i%3C/p%3E
        // 目的是讓collectionview的header可以像tableview的header一樣, 可以停留
        
        // 創建一個索引集.(NSIndexSet:唯一的,有序的,無符號整數的集合)
        let missingSections = NSMutableIndexSet()
        // 遍歷, 獲取當前屏幕上的所有section
        for layoutAttributes in answer {
            // 如果是cell類型, 就加入索引集裡面
            if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {
                missingSections.addIndex(layoutAttributes.indexPath.section)
            }
        }
        
        // 遍歷, 將屏幕中擁有header的section從索引集中移除
        for layoutAttributes in answer {
            // 如果是header, 移掉所在的數組
            if (layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader) {
                missingSections .removeIndex(layoutAttributes.indexPath.section)
            }
        }
        
        // 遍歷當前屏幕沒有header的索引集
        missingSections.enumerateIndexesUsingBlock { (idx, _) in
            // 獲取section中第一個indexpath
            let indexPath = NSIndexPath(forItem: 0, inSection: idx)
            // 獲取其UICollectionViewLayoutAttributes
            let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)
            // 如果有值, 就添加到UICollectionViewLayoutAttributes數組中去
            if let _ = layoutAttributes{
                answer.append(layoutAttributes!)
            }
        }
        
        // 遍歷UICollectionViewLayoutAttributes數組, 更改header的值
        for layoutAttributes in answer {
            // 如果是header, 改變其參數
            if (layoutAttributes.representedElementKind==UICollectionElementKindSectionHeader) {
                // 獲取header所在的section
                let section = layoutAttributes.indexPath.section
                // 獲取section中cell總數
                let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section)
                // 獲取第一個item的IndexPath
                let firstObjectIndexPath = NSIndexPath(forItem: 0, inSection: section)
                // 獲取最後一個item的IndexPath
                let lastObjectIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)
                
                // 定義兩個變數來保存第一個和最後一個item的layoutAttributes屬性
                var firstObjectAttrs : UICollectionViewLayoutAttributes
                var lastObjectAttrs : UICollectionViewLayoutAttributes
                
                // 如果當前section中cell有值, 直接取出來即可
                if (numberOfItemsInSection > 0) {
                    firstObjectAttrs =
                        self.layoutAttributesForItemAtIndexPath(firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForItemAtIndexPath(lastObjectIndexPath)!
                } else { // 反之, 直接取header和footer的layoutAttributes屬性
                    firstObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstObjectIndexPath)!
                    lastObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastObjectIndexPath)!
                }
                // 獲取當前header的高和origin
                let headerHeight = CGRectGetHeight(layoutAttributes.frame)
                var origin = layoutAttributes.frame.origin
                
                origin.y = min(// 2. 要保證在即將消失的臨界點跟著消失
                    max( // 1. 需要保證header懸停, 所以取最大值
                        collectionView!.contentOffset.y  + collectionView!.contentInset.top,
                        (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)
                    ),
                    (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)
                )
                
                // 預設的層次關係是0. 這兒設置大於0即可.為什麼設置成1024呢?因為我們是程式猿...
                layoutAttributes.zIndex = 1024
                layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size)
                
            }
            
        }
        
        return answer;
    }
    
    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        // 返回true, 表示一旦進行滑動, 就實時調用上面的-layoutAttributesForElementsInRect:方法
        return true
    }

}

⑩+@end: 整個項目, 東西還是蠻多的, 也不是僅僅幾百上千字能說清楚的, 幾乎每一個頁面, 每一個文件, 我都有詳細的中文註釋. 希望大家一起進步. 這也是我的第一個開源的完整的Swift 項目, 有什麼不足或者錯誤的地方, 希望大家指出來, 萬分感激!!!

下載地址

github地址

如果對您有些許幫助, 請☆star

後續

可能有些功能模塊存在bug, 後續我都會一一進行修複和完善的, 並更新在github上.

如果您有任何疑問,或者發現bug以及不足的地方, 可以在下麵給我留言, 或者關註我的新浪微博, 給我私信.

聯繫我

github

新浪微博

簡書


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. 概要 在 iOS 設備中,照片和視頻是相當重要的一部分。最近剛好在製作一個自定義的 iOS 圖片選擇器,順便整理一下 iOS 中對照片框架的使用方法。在 iOS 8 出現之前,開發者只能使用 AssetsLibrary 框架來訪問設備的照片庫,這是一個有點跟不上 iOS 應用發展步伐以及代碼設 ...
  • 遞歸搜尋NSString中重覆的文本 效果 源碼 https://github.com/YouXianMing/iOS-Project-Examples 中的 StringRange 項目 ...
  • 在用戶使用app過程中,會產生各種各樣的事件 iOS中的事件可以分為3大類型:觸摸事件、加速計事件、遠程式控制制事件 在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收並處理事件。我們稱之為“響應者對象” UIApplication、UIViewController、UI ...
  • 1、如何通過代碼設置Button title的字體大小 設置Button.titleLabel.font = [UIFont systemFontOfSize:<#(CGFloat)#>] ; 2、獲取當前時間 3、判斷字元串是否為空字元的方法 4、tableView優化 5、百度地圖自定義大頭針圖 ...
  • 添加按鈕 ...
  • 使用Genymotion調試出現錯誤INSTALL_FAILED_CPU_ABI_INCOMPATIBLE解決辦法 http://www.cnblogs.com/xiaobo-Linux/ 下載地址:http://files.cnblogs.com/files/xiaobo-Linux/genymo ...
  • 一、NSAttributeString簡介 NSAttributedString叫做富文本,是一種帶有屬性的字元串,通過它可以輕鬆的在一個字元串中表現出多種字體、字型大小、字體大小等各不相同的風格,還可以對段落進行格式化。 二、字元屬性 1.NSString *const NSFontAttribute ...
  • 介紹 最近用淘寶客戶端的時候,編輯地址的時候有個地區選擇的功能。看上面的效果覺得挺酷,滾動的時候,是最後一個從下麵飛上來挨著前一個。就自己鼓搗一個出來玩玩。 說了效果可能不太直觀,下麵上兩張圖看看效果 淘寶地區選擇效果 再來一張自己的效果 gif的效果可能不太好,大家自己用Android手機打開淘寶 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...