Swift practice demo
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
FoodPin.xcodeproj
FoodPin
README.md

README.md

纯Swift代码构建一个功能完善的APP

效果演示

iOS11之后,导航栏可以设置这样变大的效果。

在 ViewController 的 viewDidLoad() 方法中添加下面这行代码可以实现:

	// iOS11之后这个属性可以让导航栏往下滑动的时候title变大
    navigationController?.navigationBar.prefersLargeTitles = true

向右滑动菜单:

向左滑动菜单:

tableView,actionSheet

详情页面

导航栏透明,并修改大字体状态的title颜色, viewDidLoad()

    navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
    navigationController?.navigationBar.shadowImage = UIImage()
    
    // 设置导航栏title的大字体状态的颜色
    if let customFont = UIFont(name: "PingFangSC-Medium", size: 40.0) {
        navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor(red: 231.0/255.0, green: 76.0/255.0, blue: 60.0/255.0, alpha: 1.0), NSAttributedString.Key.font: customFont]
    }

详情页面的导航栏变透明,返回按钮变色

        navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
        navigationController?.navigationBar.shadowImage = UIImage()
        navigationController?.navigationBar.tintColor = UIColor.white

调整tableView的顶部位置

        detailTableView.contentInsetAdjustmentBehavior = .never

全局修改导航栏的返回按钮 application(_:didFinishLaunchingWithOptions:) 中添加

        let backButtonImage = UIImage(named: "back")
        UINavigationBar.appearance().backIndicatorImage = backButtonImage
        UINavigationBar.appearance().backIndicatorTransitionMaskImage = backButtonImage

修改详情页状态栏,

    /// 状态栏颜色
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }

可以没有生效,因为会用导航栏controller的颜色,为了让可以针对性修改页面,加一个Extension文件,UINavigationController+Ext.swift

import UIKit

extension UINavigationController {
    open override var childForStatusBarStyle: UIViewController? {
        return topViewController
    }
}

添加地图信息

自定义 annotationView,实现 MKMapViewDelegate

    //MARK: - MKMapViewDelegate
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        let identifier = "MyMarker"
        
        if annotation.isKind(of: MKUserLocation.self) {
            return nil
        }
        
        // Reuse the annotation if possible
        var annotationView: MKMarkerAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView
        
        if annotationView == nil {
            annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
        }
        
        annotationView?.glyphText = "😋"
        annotationView?.markerTintColor = UIColor.orange
        
        return annotationView
    }

        mapView.showsTraffic = true
        mapView.showsScale = true
        mapView.showsCompass = true

测试一些动画

代理回调,将选择的表情回调给详情页,展示在 headerView 的右下角

静态列表,textField使用

图片选择器

改用 CoreData 存储数据,并用 NSFetchedResultsController 监听;新建局部刷新首页 tableview

删除后,局部刷新首页 tableview

    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (action, soureView, comletionHandler) in
            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
                let context = appDelegate.persistentContainer.viewContext
                
                if let currentVC = self.currentViewController() as? YQRestaurantTableViewController {
                    let restaurantToDelete = currentVC.fetchResultController.object(at: indexPath)
                    context.delete(restaurantToDelete)
                    
                    appDelegate.saveContext()
                }
                
            }
            
            comletionHandler(true)
        }

更新rating 表情,同样是数据库级别的更新,加 appDelegate.saveContext() 就可以

    //MARK: - YQRestaurantReviewViewControllerDelegate
    func onClickRateButtonInReviewVC(rate: RateModel) {
        restaurant.rating = rate.image
        refreshRatingImageView(rateImage: rate.image)
        
        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
            appDelegate.saveContext()
        }
    }

添加搜索栏,支持name搜索,搜索状态时禁止左右滑动的编辑态

tabBarController

About页面;使用元组;分别用 WKWebViewSFSafariViewController 打开web页面

使用iCloud,在 CloudKit Dashboard 中创建数据;原本是想上传图片,但 Dashboard 一上传那图片就 停止响应了,苹果这个做的有点坑==

然后用 Convenience API:

    func fetchRecordsFromCloud() {
        let cloudContainer = CKContainer.default()
        let publicDatabase = cloudContainer.publicCloudDatabase
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "Restaurant", predicate: predicate)

        publicDatabase.perform(query, inZoneWith: nil) { (results, error) in
            if let error = error {
                print(error)
                return
            }

            if let results = results {
                print("Completed the download of Restaurant data")
                self.restaurants = results
                DispatchQueue.main.async(execute: {
                    self.discoverTableView.reloadData()
                })
            }
        }
    }

代码中拉取到的数据结构:

改用 Operational API。因为 Convenience API 不能只请求携带某些字段,它会把所有数据都完整拉下来

    func fetchRecordsFromCloud() {
        let cloudContainer = CKContainer.default()
        let publicDatabase = cloudContainer.publicCloudDatabase
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "Restaurant", predicate: predicate)
        
        let queryOperation = CKQueryOperation(query: query)
        queryOperation.desiredKeys = ["name", "iamge"]
        queryOperation.queuePriority = .veryHigh
        queryOperation.resultsLimit = 50
        queryOperation.recordFetchedBlock = { (record) -> Void in
            self.restaurants.append(record)
        }
        
        queryOperation.queryCompletionBlock = { [unowned self] (cursor, error) -> Void in
            if let error = error {
                print("Failed to get data from iCloud -\(error.localizedDescription)")
                
                return
            }
            
            print("Successfully retrieve the data from iCloud")
            DispatchQueue.main.async(execute: {
                self.discoverTableView.reloadData()
            })
        }
        
        publicDatabase.add(queryOperation)
    }

使用 UIActivityIndicatorViewUIRefreshControl

将数据存储到iCloud

    func saveRecordToCloud(restaurant: RestaurantMO!) -> Void {
        // Prepare the record to save
        let record = CKRecord(recordType: "Restaurant")
        record.setValue(restaurant.name, forKey: "name")
        record.setValue(restaurant.type, forKey: "type")
        record.setValue(restaurant.location, forKey: "location")
        record.setValue(restaurant.phone, forKey: "phone")
        record.setValue(restaurant.summary, forKey: "description")
        
        let imageData = restaurant.image! as Data
        
        // Resize the image
        let originalImage = UIImage(data: imageData)!
        let scalingFactor = (originalImage.size.width > 1024) ? 1024 / originalImage.size.width : 1.0
        let scaledImage = UIImage(data: imageData, scale: scalingFactor)
        
        // Write the image to local file for temporary use
        let imageFilePath = NSTemporaryDirectory() + restaurant.name!
        let imageFileURL = URL(fileURLWithPath: imageFilePath)
        try? scaledImage?.jpegData(compressionQuality: 0.8)?.write(to: imageFileURL)
        
        // Create image asset for upload
        let imageAsset = CKAsset(fileURL: imageFileURL)
        record.setValue(imageAsset, forKey: "image")
        
        // Get the Public iCloud Database
        let publicDatabase = CKContainer.default().publicCloudDatabase
        
        // Save the record to iCloud
        publicDatabase.save(record, completionHandler: { (record, error) -> Void in
            // Remove temp file
            try? FileManager.default.removeItem(at: imageFileURL)
        })
    }