• 原文:Top 10 iOS Swift libraries every iOS developer should know about swift每天都在变的越来越流行。如果你正开始一个新项目,你有机会决定使用swift。为了你转变(从ios转swift)更容易和节省你造轮子的时间,...

    原文:Top 10 iOS Swift libraries every iOS developer should know about

    swift每天都在变的越来越流行。如果你正开始一个新项目,你有机会决定使用swift。为了你转变(从ios转swift)更容易和节省你造轮子的时间,下面是我们认为每个ios开发者都应该知道的10个三方库。

    就像我们在Top 5 iOS libraries every iOS developer should know about这篇文章提到的一样,Github和Bitbucker是发现开源库的好地方,像CocoaPods和Carthage这样的工具可以帮你快速安装和管理你项目中的三方库,让你管理依赖更简单。

    swift

    1.Alamofire

    当你想要抽象简化App中的网络请求时,Alamofire是你需要的,Alamofire是一个Http网络请求库,构建在NSURLSession和基础URL加载系统之上,它用简单优雅的接口很好的封装了网络请求。

    // Making a GET request
    Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
             .responseJSON { response in
                 print(response.request)  // original URL request
                 print(response.response) // URL response
                 print(response.data)     // server data
                 print(response.result)   // result of response serialization
    
                 if let JSON = response.result.value {
                     print("JSON: \(JSON)")
                 }
             }
    

    2.SwiftyJSON

    swift的Explicit types(显示类型)可以确保我们不会在代码中犯错和出现bug。但是有时处理起来还是比较麻烦,特别是和JSON打交道的时候。幸运的是,SwiftyJSON提供了可读性更好的方式帮我们处理JSON数据。还提供了可选的自动解析!

    // Typical JSON handling
    
    if let statusesArray = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [[String: AnyObject]],
        let user = statusesArray[0]["user"] as? [String: AnyObject],
        let username = user["name"] as? String {
        // Finally we got the username
    }
    
    // With SwiftyJSON
    
    let json = JSON(data: dataFromNetworking)
    if let userName = json[0]["user"]["name"].string {
      //Now you got your value
    }
    

    SwiftyJson也可以很好的和Alamofire配合使用。

    Alamofire.request(.GET, url).validate().responseJSON { response in
        switch response.result {
        case .Success:
            if let value = response.result.value {
              let json = JSON(value)
              print("JSON: \(json)")
            }
        case .Failure(let error):
            print(error)
        }
    }
    

    3.ObjectMapper

    如果你写过一个通过API获取信息的app,你可能需要花大量时间写代码把你的响应结果映射为你的object。ObjectMapper可以帮你把JSON格式响应结果转换成你的model对象,反之亦然。换句话说,它帮你把JSON映射成对象,也可以把对象转换成JSON。嵌套的对象也支持。

    // Temperature class that conforms to Mappable protocol
    
    struct Temperature: Mappable {
        var celsius: Double?
        var fahrenheit: Double?
    
        init?(_ map: Map) {
    
        }
    
        mutating func mapping(map: Map) {
            celsius      map["celsius"]
            fahrenheit   map["fahrenheit"]
        }
    }
    

    AlamofireObjectMapper也值得提一下,一个Alamofire的扩展使用ObjectMapper将JSON响应数据转换成swift对象。

    4.Quick

    Quick是一个行为驱动(BDD)开发框架,它的灵感来自于
    RSpec,Specta, 和Ginkgo。配合Nimble一起使用,Nimble是一个测试匹配框架。

    // Documentation directory spec
    
    class TableOfContentsSpec: QuickSpec {
      override func spec() {
        describe("the 'Documentation' directory") {
          it("has everything you need to get started") {
            let sections = Directory("Documentation").sections
            expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))
            expect(sections).to(contain("Installing Quick"))
          }
    
          context("if it doesn't have what you're looking for") {
            it("needs to be updated") {
              let you = You(awesome: true)
              expect{you.submittedAnIssue}.toEventually(beTruthy())
            }
          }
        }
      }
    }
    

    5.Eureka

    Eureka可以帮你简单优雅的实现动态table-view表单。它由rows,sections和forms组成。如果你的app包含大量表单,Eureka可以真正帮你节省时间。

    // Creating a form
    
    class CustomCellsController : FormViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            form +++ Section("Custom cells")
                        <<< WeekDayRow(){
                            $0.value = [.Monday, .Wednesday, .Friday]
                        }
                        <<< TextFloatLabelRow() {
                            $0.title = "Float Label Row, type something to see.."
                        }
        }
    }
    

    6.RxSwift

    RxSwift是一个基于Swift的的函数式响应编程框架。更具体点,RxSwift是是Rx的一个Swift语言版本(还有java版本RxJava,js->RxJs)它的目标是让异步和事件数据流操作更简单。KVO observing, async operations and delegates are all unified under abstraction of sequence(还没来的及学习掩面哭),如果你已经使用过ReactiveCocoa,你接受起来会比较简单(都是函数式编程思想)

    // Combine first and last name sequences, and bind the resulting string to label
    
    combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
                .map { "Greeting \($0)" }
                .bindTo(greetingLabel.rx_text)
    

    7.SnapKit

    SnapKit是一个用少量代码写出不丢可读性auto layout的AutoLayout库。

    // Resizing and centering subview in its superview
    
    let box = UIView()
    let container = UIView()
    
    container.addSubview(box)
    
    box.snp_makeConstraints { (make) 
     Void in
        make.size.equalTo(50)
        make.center.equalTo(container)
    }
    

    8.Spring

    Spring是一个可以帮你用代码或者直接在Storybard创建动画的动画库,在Storyboard你可以用runtime(通过设置IBInspectable属性)来创建动画,Spring已经成长为一个全面发展的动画库 支持很多已经存在的动画。

    // Usage with code
    
    layer.animation = "wobble"
    layer.animate()
    

    9.Kingfisher

    Kingfisher是一个轻量的下载和缓存网络图片库。下载和缓存是异步进行操作,已经下载好的图片会缓存在内存和本地,极大得提高app的体验。

    // Basic example of downloading and caching an image
    
    imageView.kf_setImageWithURL(NSURL(string: "http://your_image_url.png")!)
    

    10.CoreStore

    CoreStore是一个基于Core Data的封装库。它的目标是安全优雅和Core Data进行交互。CoreStore的API提供了常用的有效的方法让你和你的数据库进行交互。

    // Storing a person entity
    
    CoreStore.beginAsynchronous { (transaction) 
     Void in
        let person = transaction.create(Into(MyPersonEntity))
        person.name = "John Smith"
        person.age = 42
    
        transaction.commit { (result) 
     Void in
            switch result {
                case .Success(let hasChanges): print("success!")
                case .Failure(let error): print(error)
            }
        }
    }
    

    你的看法呢

    你不认同?一些其他的三方库应该出现在这个列表?给我们留言让我们知道你的想法!

    展开全文
  • EasyLayout 是一个服务于ios,基于swift编写的布局框架。它定义了运算符,通过编写view之间的关系生成对应的NSLayoutConstraint对象,简化了NSLayoutConstraint对象的生成代码,它使得约束的显示方式和xib的约束显示...

    EasyLayout 简介

    EasyLayout 是一个服务于ios,基于swift编写的布局框架。它定义了运算符,通过编写view之间的关系生成对应的NSLayoutConstraint对象,简化了NSLayoutConstraint对象的生成代码,它使得约束的显示方式和xib的约束显示很相似

    EasyLayout通过扩展UIView和UIViewController添加API,通过编写UIView与UIView之间关系或者UIView与UIViewController的LayoutGuide之间的关系就可以生成对应的NSLayoutConstraint

    项目和Demo地址: https://github.com/wangmuhuo/EasyLayoutDemo.git

    使用事例

    swift代码:

    //
    //  ViewController.swift
    //  EasyLayoutDemo
    //
    //  Created by Jejay on 17/3/14.
    //  Copyright © 2017年 jejay. All rights reserved.
    //
    
    import UIKit
    import EasyLayout
    
    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            let view1: UIView = {
                let view = UIView()
                view.backgroundColor = UIColor.redColor()
                return view
            }()
            let view2: UIView = {
                let view = UIView()
                view.backgroundColor = UIColor.greenColor()
                return view
            }()
            
            self.view.addSubview(view1)
            self.view.addSubview(view2)
            
            view1.translatesAutoresizingMaskIntoConstraints = false
            view2.translatesAutoresizingMaskIntoConstraints = false
            
            self.view.addConstraints([
                view1.es.top .= self.es_topLayoutGuideBottom .+ 50,
                view1.es.centerX .= self.view.es.centerX,
                view1.es.height .= 50,
                view1.es.width .= self.view.es.width .* 0.5,
                
                view2.es.top .= view1.es.bottom .+ 50,
                view2.es.centerX .= view1.es.centerX,
                view2.es.height .= view1.es.height .* 2 .+ 50,
                view2.es.width .= 1000 .| 900,
                view2.es.width .<= view1.es.width .- 50,
                
                ])
            
        }
        
    }
    
    

    效果:


    EasyLayout api 简单介绍


    目录文件结构

    ESLayout.swift  提供布局相关api

    ESNumberConversion.swift 提供数值类型相关api

    布局属性

    和 NSLayoutAttribute对应,我就不多说了,
    1、UIView的布局属性:通过 view.es. 就可以通过联想得到,
    2、UIViewController的LayoutGuide的布局属性:通过viewController.es_ 就可以通过联想得到

    运算符

    考虑到很运算符与系统冲突,所以本框架运算符前面都带有点(.),这点是 模仿Matlab运算符来设计的,matlab中,两个矩阵 A和B,A*B 表示两个矩阵之间进行矩阵相乘运算,A.*B 表示矩阵内部元素对应相乘


    一、 .= 、.<= .>=:

    双目运算符,参数是两个布局属性,用于创建两个布局属性之间的约束关系,分别对应于系统NSLayoutRelation下的 .Equal、.LessThanOrEqual、.GreaterThanOrEqual

    如:

    let c1 = view1.es.height .= 50

    等同于:

    let c1 = NSLayoutConstraint(

                item: view1, attribute: NSLayoutAttribute.Height,

                relatedBy: .Equal,

                toItem: nil, attribute: .NotAnAttribute,

                multiplier: 1,

                constant: 50)


    二、 .*、./、.+ 、.-:

    双目运算符,参数是属性和一个数值类型,用于给属性增加偏移或者倍数关系,对应NSLayoutConstraint的constant属性和multiplier属性

    如:

    let c2 = view1.es.height .= view2.es.height .* 2

    等同于:

    let c2 = NSLayoutConstraint(

                item: view1, attribute: NSLayoutAttribute.Height,

                relatedBy: .Equal,

                toItem: view2, attribute: NSLayoutAttribute.Height,

                multiplier: 2,

                constant: 0)


    三、   .|:

    双目运算符,参数是NSLayoutConstraint和一个数值类型,用于给属性增加权重值,对应NSLayoutConstraint的priority

    如:

    let c3 = view1.es.height .= 50 .| 500

    等同于以下两句代码:

    let c3 = NSLayoutConstraint(

                item: view1, attribute: NSLayoutAttribute.Height,

                relatedBy: .Equal,

                toItem: nil, attribute: .NotAnAttribute,

                multiplier: 1,

                constant: 50)

    c3.priority = 500




    展开全文
  • UIKit框架-高级控件Swift版本: 10.UIWebView方法/属性详解

    前面我们已经讲解完了 UINavigationController 的一些常用属性以及方法, 现在让我们来看看一个关于网络的 UIWebView.


    1.UIWebView的常用属性

    // 1.设置 UIWebView 的代理对象
    var delegate: UIWebViewDelegate?
    
    // 2.获取 UIWebView 的 UIScrollView 属性
    var scrollView: UIScrollView { get }
    
    // 3.获取 UIWebView 的网络请求
    var request: NSURLRequest? { get }
    
    // 4.获取 UIWebView 是否在接收数据的时候可以退后一步
    var canGoBack: Bool { get }
    
    // 5.获取 UIWebView 是否在接收数据的时候可以前进一步
    var canGoForward: Bool { get }
    
    // 6.获取 UIWebView 是否接受完数据
    var loading: Bool { get }
    
    // 7.设置 UIWebView 接收的数据是否可以通过手势来调整页面内容大小
    var scalesPageToFit: Bool
    
    // 8.设置 UIWebView 接收的数据是什么类型
    var dataDetectorTypes: UIDataDetectorTypes
    
    // 9.设置 UIWebView 内是否可以回放媒体
    var allowsInlineMediaPlayback: Bool
    
    // 10.设置 UIWebView 中的 HTML5 视频是否自动播放(默认是True)
    var mediaPlaybackRequiresUserAction: Bool
    
    // 11.设置 UIWebView 是否可以使用 Air 播放(默认是True)
    var mediaPlaybackAllowsAirPlay: Bool
    
    // 12.设置 UIWebView 是否使用同步加载(默认是False)
    var suppressesIncrementalRendering: Bool
    
    // 13.设置 UIWebView 在点击视图或者元素时是否显示键盘(默认是True)
    var keyboardDisplayRequiresUserAction: Bool
    
    // 14.设置 UIWebView 的分页模式
    var paginationMode: UIWebPaginationMode
    
    // 15.设置 UIWebView 的分页打破模式
    var paginationBreakingMode: UIWebPaginationBreakingMode
    
    // 16.设置 UIWebView 的分页长度
    var pageLength: CGFloat
    
    // 17.设置 UIWebView 之间的页面宽度
    var gapBetweenPages: CGFloat
    
    // 18.获取 UIWebView 的页面数量
    var pageCount: Int { get }

    常用类型

    // 1.获取 UIWebView 的数据类型
    struct UIDataDetectorTypes : RawOptionSetType {
        init(_ rawValue: UInt)
        init(rawValue: UInt)
    
        static var PhoneNumber: UIDataDetectorTypes { get }
        static var Link: UIDataDetectorTypes { get }
        static var Address: UIDataDetectorTypes { get }
        static var CalendarEvent: UIDataDetectorTypes { get }
        static var None: UIDataDetectorTypes { get }
        static var All: UIDataDetectorTypes { get }
    }
    
    // 2.设置 UIWebView 的 Navigation 类型
    enum UIWebViewNavigationType : Int {
        case LinkClicked
        case FormSubmitted
        case BackForward
        case Reload
        case FormResubmitted
        case Other
    }
    
    
    // 3.设置 UIWebView 的分页样式
    enum UIWebPaginationMode : Int {
        case Unpaginated
        case LeftToRight
        case TopToBottom
        case BottomToTop
        case RightToLeft
    }
    
    // 4.设置 UIWebView 打破分页样式的样式
    enum UIWebPaginationBreakingMode : Int {
        case Page
        case Column
    }

    2.UIWebView的代理方法

    // 1.该方法是用来设置是否在 UIWebView 加载之前发送一个请求
        optional func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool
    
    // 2.该方法是在 UIWebView 在开发加载时调用
        optional func webViewDidStartLoad(webView: UIWebView)
    
    // 3.该方法是在 UIWebView 加载完之后才调用
        optional func webViewDidFinishLoad(webView: UIWebView)
    
    // 4.该方法是在 UIWebView 请求失败的时候调用
        optional func webView(webView: UIWebView, didFailLoadWithError error: NSError)

    3.代码示范

    首先我们要使用storyBoard布局界面
    1

    关联控件
    2

    遵守代理协议

    class ViewController: UIViewController, UIWebViewDelegate {
    }

    自定义UIWebVIew

        func myWebView() {
    
            // 1.获取 UIWebView 的 UIScrollView 属性
            let webScroll = webView.scrollView
            println("webScroll = \(webScroll)")
    
            // 2.获取 UIWebView 的 request 属性
            let webRequest = webView.request
            println("webRequest = \(webRequest)")
    
            // 3.获取 UIWebView 是否在接收数据的时候可以退后一步
            let webCanBack = webView.canGoBack
            println("webCanBack = \(webCanBack)")
    
            // 4.获取 UIWebView 是否在接收数据的时候可以前进一步
            let webCanGo = webView.canGoForward
            println("webCanGo = \(webCanGo)")
    
            // 5.获取 UIWebVIew 是否接受完数据
            let webLoading = webView.loading
            println("webLoading = \(webLoading)")
    
            // 6.设置 UIWebView 接收的数据是否可以通过手势来调整页面内容大小
            webView.scalesPageToFit = true
    
            // 7.设置 UIWebView 接收到得数据是什么类型的
            webView.dataDetectorTypes = UIDataDetectorTypes.All
    
            // 8.设置 UIWebView 内是否可以回放媒体
            webView.allowsInlineMediaPlayback = true
    
            // 9.设置 UIWebView 中的 HTML5 视频是否自动播放
            webView.mediaPlaybackRequiresUserAction = true
    
            // 10.设置 UIWebView 是否可以使用 Air 播放
            webView.mediaPlaybackAllowsAirPlay = true
    
            // 11.设置 UIWebView 是否使用同步加载(默认是False)
            webView.suppressesIncrementalRendering = false
    
            // 12.设置 UIWebView 在点击视图或者元素时是否显示键盘(默认是True)
            webView.keyboardDisplayRequiresUserAction = true
    
            // 13.设置 UIWebView 的分页模式
            webView.paginationMode = UIWebPaginationMode.RightToLeft
    
            // 14.设置 UIWebView 的分页打破模式
            webView.paginationBreakingMode = UIWebPaginationBreakingMode.Column
    
            // 15.设置 UIWebView 的分页长度
            webView.pageLength = 400
    
            // 16.设置 UIWebView 每个分页之间的宽度
            webView.gapBetweenPages = 100
    
            // 17.获取 UIWebView 的分页数量
            let webViewPageCount = webView.pageCount
            println(webViewPageCount)
    
            // 18.设置 UIWebView 的代理对象
            webView.delegate = self
    
            // 19.发送网络请求
            var url:NSURL = NSURL(string:"http://www.baidu.com")!
            var request:NSURLRequest = NSURLRequest(URL:url)
            webView.loadRequest(request)
        }

    实现代理方法

        // 1.该方法是用来设置是否在 UIWebView 加载之前发送一个请求
        func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
            return true
        }
    
        // 2.该方法是在 UIWebView 在开发加载时调用
        func webViewDidStartLoad(webView: UIWebView) {
            println("开始加载")
        }
    
        // 3.该方法是在 UIWebView 加载完之后才调用
        func webViewDidFinishLoad(webView: UIWebView) {
            println("加载完成")
        }
    
        // 4.该方法是在 UIWebView 请求失败的时候调用
        func webView(webView: UIWebView, didFailLoadWithError error: NSError) {
            println("加载失败")
        }
    

    在 ViewDidLoad 方法中实现

        override func viewDidLoad() {
            super.viewDidLoad()
            self.myWebView()
        }

    PS: UIWebView 继承与 UIView, 并且遵守了 UIScrollViewDelegate 协议, 所以它可以使用里面的方法和属性.


    好了, 这次我们就讲到这里, 下次我们继续~~

    展开全文
  • TangramKit是 iOS 系统下用 Swift 编写的第三方界面布局框架。他集成了 iOS 的 AutoLayout 和 SizeClass 以及 Android 的五大容器布局体系以及 HTML/CSS 中的 float 和 flex-box 的布局功能和思想,目的是为 iOS ...

    由于字数限制,本篇介绍文有所删减,详细可前往介绍地址查看详细

    OC 版本介绍地址: http://www.code4app.com/thread-7501-1-1.html

    Swift 版本介绍地址: http://www.code4app.com/thread-11767-1-1.html

    ##前言

    ***TangramKit是 iOS 系统下用 Swift 编写的第三方界面布局框架。他集成了 iOS 的 AutoLayout 和 SizeClass 以及 Android 的五大容器布局体系以及 HTML/CSS 中的 float 和 flex-box 的布局功能和思想,目的是为 iOS 开发人员提供一套功能强大、多屏幕灵活适配、简单易用的 UI 布局解决方案。 Tangram 的中文即七巧板的意思,取名的寓意表明这个布局库可以非常灵巧和简单的解决各种复杂界面布局问题。他的同胞框架:MyLayout***是一套用 objective-C 实现的界面布局框架。二者的主体思想相同,实现原理则是通过扩展 UIView 的属性,以及重载layoutSubviews方法来完成界面布局,只不过在一些语法和属性设置上略有一些差异。可以这么说 TangramKit 是 MyLayout 布局库的一个升级版本。大家可以通过访问下面的 github 站点去下载最新的版本:

    TangramKit 演示效果图

    ##所见即所得和编码之争以及屏幕的适配 在我 10 多年的开发生涯中,大部分时间都工作在客户端上。从 DOS 到 Windows 再到 UNIX 再到 2010 年接触 iOS 开发这 6 年多的时间中,总感觉一无所获,原因呢是觉没有什么积累。作为一个以编程为职业的人来说如果不留下什么可以值得为大家所知的东西的话,那将是一种职业上的遗憾。 就像每个领域都有工作细分一样,现在的编程人员也有明确分工:有一部分人做的是后端开发的工作,而有一部分人做的是前端开发的工作。二者相辅相成而完成了整个系统。后端开发的重点在于实现高性能和高可用,在数据处理上通常都是一个输入一个加工然后一个输出;而前端开发的重点在于实现界面流畅性和美观性,在数据处理上往往是多个输入一个加工和多个输出。在技术层面上后端处理的对象是多线程多进程以及数据,而前端处理的对象则是图形绘制和以及界面布局和动画特效。 这篇文章的重点是介绍界面布局的核心,因此其他部分就不再展开去说了。对于一个 UI 界面来说,好的界面布局体系往往能起到事半工倍的作用。 PC 设备上因为屏幕总是够大,比如 VB,VF,PB,Dephi,AWT,Swing 等语言或者环境下的应用开发非常方便, IDE 环境中提供一个所见即所得的开发面板(form),人们只要使用简单的拖拉拽动作就可把各种界面元素加入到 form 中就可以形成一个小程序了。而开发 VC 程序则相对麻烦,系统的 IDE 环境对可视化编程的支持没有那么的完善,因此大部分界面的构建都需要通过编码来完成。同时因为 PC 设备屏幕较大而且标准统一,因此几乎不存在界面要在各种屏幕尺寸适配的问题。唯一引起争议是可视化编程和纯代码编程的方式之争,这种争议也体现在 iOS 应用的开发身上,那就是用 XIB 和 SB 以及纯代码编写界面的好坏争议。关于这个问题个人的意见是各有各好: XIB/SB 进行布局时容易上手且所见即所得,但缺乏灵活性和可定制化;而纯代码则灵活性高可定制化强,缺点是不能所见即所得和代码维护以及系统分层模糊。 再回到屏幕适配的话题来说,如果说 PC 时代编程屏幕尺寸适配不是很重要的工作,那么到了移动设备时代则不一样了,适配往往成为整个工作的重点和难点。主要的原因是设备的屏幕尺寸和设备分辨率的多样性的差异,而且要求在这么小的屏幕上布局众多的要素,同时又要求界面美观和友好的用户体验,这就非常考验产品以及 UI/UE 人员和开发人员的水平,同时这部分工作也占用了开发者的大部分时间。在现有的两个主流的移动平台上, Android 系统因为本身硬件平台差异性的原因,为了解决这些差异性而设计了一套非常方便的和友好的界面布局体系。它提出了布局容器的概念,也就是有专门职责的布局容器视图来管理和排列里面的子视图,根据实际中的应用场景而把这些负责布局的容器视图分类抽象出了线性布局、相对布局、框架布局、表格布局、绝对布局这 5 大容器布局,而这些也就构成了 Android 系统布局体系的核心实现。也正是这套布局机制使得 Android 系统能够方便的胜任多种屏幕尺寸和分辨率在不同硬件设备上的 UI 界面展示。而对于 iOS 的开发人员来说,早期的设备只有单一的 3.5in 大小且分辨率也只有 480x320 和 960x640 这两种类型的设备,因此开发人员只需要采用绝对定位的方式通过视图的frame属性设置来实现界面的布局,根本不需要考虑到屏幕的适配问题。但是这一切从苹果后续依次发布 iPhone4/5/6/7 系列的设备后被打破了,整个 iOS 应用的开发也需要考虑到多屏幕尺寸和多分辨率的问题了,这样原始的frame方法进行布局设置将不能满足这些多屏幕的适配问题了,因此 iOS 提出了一套新的界面布局体系: AutoLayout 以及 SizeClass. 这套机制通过设置视图之间的位置和尺寸的约束以及对屏幕尺寸进行分类的方式来完成界面的布局和屏幕的适配工作。 尽管如此, 虽然两个移动端平台都提供了自己独有且丰富的界面布局体系,但对于移动客户端开发人员来说界面布局和适配仍然是我们在开发中需要重点关注的因素之一。

    ##布局的核心 我们知道,在界面开发中我们直接操作的对象是视图,视图可以理解为一个具有特定功能的矩形区块,因此所谓的布局的本质就是为视图指定某个具体的尺寸以及指定其排列在屏幕上的位置。因此布局的动作就分为两个方面:一个是指定视图的尺寸,一个是指定视图的位置。

    视图的尺寸和位置

    视图的尺寸

    视图的尺寸就是指视图矩形块的大小,为了表征视图的大小我们称在屏幕水平方向的尺寸大小为宽度,而称在屏幕垂直方向的尺寸大小为高度,因此一个视图的尺寸我们就可以用宽度和高度两个维度的值来描述了,宽度和高度的单位我们称之为点。UIView 中用bounds属性的 size 部分来描述视图的尺寸(bounds 属性的 origin 部分后面会介绍到)。 对于屏幕尺寸来说同样也用宽度和高度来描述。在视图层次体系结构中的顶层视图的尺寸和屏幕的尺寸是一致的,为了描述这个特殊的顶层视图我们将这个顶层根视图称之为窗口,窗口的尺寸和屏幕的尺寸一样大,同时窗口是一切视图的容器视图。一个视图的尺寸我们可以用一个具体的数值来描述,比如某个视图的宽度和高度分别为:100x200。我们称这种定义的方式为绝对值类型的尺寸。但是在实际中我们的一些视图的尺寸并不能够一开始就被明确,原因是这些视图的尺寸大小和其他视图的尺寸大小有关,也就是说视图的尺寸依赖于另外一个视图或者另外一组视图。比如说有 A 和 B 两个视图,我们定义 A 视图的宽度和 B 视图的宽度相等,而 A 视图的高度则是 B 视图高度的一半。也就是可以表述为如下:

    A.bounds.size.width = B.bounds.size.width
    A.bounds.size.height = B.bounds.size.height /2
    
    //父视图 S 的高度等于里面子视图 A,B 的高度的总和
    S.bounds.size.height = A.bounds.size.height + B.bounds.size.height 
    
    

    我们称为这种尺寸的定义方式为相对值类型的尺寸。在相对值类型的尺寸中, 视图某个维度的尺寸所依赖的另外一个视图可以是它的兄弟视图,也可以是它的父视图,也可以是它的子视图,甚至可以是它自身的其他维度。 这种视图尺寸的依赖关系是可以传递和递归的,比如 A 依赖于 B ,而 B 右依赖于 C 。 但是这种递归和传递关系不能形成一个闭环依赖,也就是说在依赖关系的最终节点视图的尺寸的值必须是一个绝对值类型或者特定的相对值类型(wrap 包裹值),否则的话我们将形成约束冲突而进入死循环的场景。

    两种尺寸约束依赖

    视图的尺寸之间的依赖关系还有两种特定的场景:

    • 某个视图的尺寸依赖于里面所有子视图的尺寸的大小或者依赖于视图内所展示的内容的尺寸,我们称这种依赖为**包裹(wrap)**。
    • 某个视图的尺寸依赖于所在父视图的尺寸减去其他兄弟视图所占用的尺寸的剩余尺寸也就是说尺寸等于父视图的尺寸和其兄弟视图尺寸的差集,我们称这种依赖为**填充(fill)**。

    可以看出包裹和填充尺寸是相对值类型中的两种特殊的类型,他所依赖的视图并不是某个具体的视图,而是一些相关的视图的集合。

    为了表征视图的尺寸以及尺寸可以设置的值的类型,我们就需要对尺寸进行建模,在 TangramKit 框架中**TGLayoutSize**类就是一个尺寸类,这个类里面的 equal 方法则是用来设置视图尺寸的各种类型的值:包括绝对值类型,相对值类型,以及包裹和填充的值类型等等。同时我们对 UIView 扩展出了两个属性tg_width, tg_height分别用来表示视图的布局宽度和布局高度。他其实是对原生的视图bounds属性中的 size 部分进行了扩充和延展。原始的bounds属性中的 size 部分只能设置绝对值类型的尺寸,而不能设置相对值类型的尺寸。

    视图的位置

    当一个视图的尺寸确定后,接下来我们就需要确定视图所在的位置了。所谓位置就是指视图在屏幕中的坐标位置,屏幕中的坐标分为水平坐标也就是 x 轴坐标,和垂直坐标也就是 y 轴坐标。而这个坐标原点在不同的系统中有区别: iOS 系统采用左手坐标系,原点都是在左上角,并且规定 y 轴在原点以下是正坐标轴,而原点以上是负坐标轴,而 x 轴则在原点右边是正坐标轴,原点左边是负坐标轴。 OSX 系统则采用右手坐标系,原点在左下角,并且规定 y 轴在原点以上是正坐标轴,而在原点以下是负坐标轴,而 x 轴则在原点右边是正坐标轴,原点左边是负坐标轴。

    不同的坐标系

    因此视图位置的确定我们需要考虑两个方面的问题:一个是位置是相对于哪个坐标系?一个是视图内部的哪个部位来描述这个位置?

    确定一个视图的位置时总是应该有一个参照物,在现有的布局体系中一般分为三种参照物:屏幕、父视图、兄弟视图

    • 第一种以屏幕坐标系作为参照来确定的位置称为绝对位置,也就是以屏幕的左上角作为原点,每个视图的位置都是距离屏幕左上角原点的一个偏移值。这种绝对位置的设置方式的优点是所有视图的参照物都是一致的,便于比较和计算,但缺点是对于那些多层次结构的视图以及带滚动效果的视图来说位置的确定则总是需要进行动态的变化和计算。比如某个滚动视图内的所有子视图在滚动时都需要重新去计算自己的位置。

    • 第二种以父视图坐标系作为参照来确定的位置称为相对位置,每个子视图的位置都是距离父视图左上角原点的一个偏移值。这样的好处就是每个子视图都不再需要关心屏幕的原点,而只需要以自己的父视图为原点进行位置的计算就可以了,这种方式是目前大部分布局体系里面采用的定位方式,也是最方便的定位方式,缺点是不同层次之间的视图的位置在进行比较时需要一步步的往上进行转换,直到转换到在窗口中的位置为止。我们称这种以父视图坐标系为原点进行定位的位置称为边距,也就是离父视图边缘的距离。

    • 第三种以兄弟视图坐标系作为参照来确定的位置称为偏移位置,子视图的位置是在关联的兄弟视图的位置的基础之上的一个偏移值。比如 A 视图在 B 视图的右边偏移 5 个点,则表示为 A 视图的左边距离 B 视图的右边 5 个点的距离。我们称这种坐标体系下的位置为间距,也就是指定的是视图之间的距离作为视图的位置。采用间距的方式进行定位只适合于同一个父视图之间的兄弟视图之间的定位方式。 各种坐标系下的定位值

    上面的三种定位方式各有优缺点,我们可以在实际中结合各种定位方式来完成视图的位置设定。

    上面我们介绍了定位时位置所基于的坐标系,因为视图并不是一个点而是一个矩形区块,所以我们必须要明确的是视图本身这个区块的哪个点来进行位置的设定。 在这里我们就要介绍视图内的坐标系。我们知道视图是一个矩形的区域,里面由无数个点构成。假如我们以视图左上角作为坐标原点的话,那么视图内的任何一点都可以用水平方向的坐标值和垂直方向的坐标值来表示。对于水平方向的坐标值来说最左边位置的点的坐标值是 0 ,最右边位置的点的坐标值是视图的宽度,中间位置的坐标点的值是宽度的一半,对于垂直方向的坐标值来说最上边位置的点的坐标值是 0 ,最下边位置的点的坐标值是视图的高度,中间位置的坐标点的值是高度的一半。我们称这几个特殊的坐标点为方位。因此一个视图一共有 9 个方位点分别是:左上、左中、左下、中上、中中、中下、右上、右中、右下。

    视图的九个方位

    通过对方位点的定义,我们就不再需要去关心这些点的具体的坐标值了,因为他描述了视图的某个特定的部位。而为了方便计算和处理,我们一般只需要指出视图内某个方位点在参照视图的坐标系里面的水平坐标轴和垂直坐标轴中的位置就可以完成视图的位置定位了,因为只要确定了这个方位点的在参照视图坐标系里面的位置,就可以计算出这个视图内的任意的一个点在参照视图坐标轴里面的位置。所谓的位置定位就是把一个视图内坐标系的某个点的坐标值映射为参照视图坐标系里面的坐标值的过程

    视图的坐标转换

    iOS 中 UIView 提供了一个属性center,**center属性的意义就是定义视图内中心点这个方位在父视图坐标系中的坐标值。我们再来考察一下 UIView 的bounds属性,上面的章节中我们有介绍bounds中的 size 部分用来描述一个视图的尺寸,而 origin 部分又是用来描述什么呢? 我们知道在左手坐标系里面,一个视图内的左上角方位的坐标值就是原点的坐标值,默认情况下原点的坐标值是(0,0)。但是这个定义不是一成不变的,也就是说原点的坐标值不一定是(0,0)。一个视图bounds里面的 origin 部分所表达的意义就是视图内左上角的坐标值, size 部分所表达的意义就是视图本身的尺寸**。这样我们就可以通过下面的公式得出一个视图内 9 个方位(再次强调方位的概念是一个视图内的坐标点的位置)的坐标值:

    左上方位 = (A.bounds.origin.x, A.bounds.origin.y)
    左中方位 = (A.bounds.origin.x,  A.bounds.origin.y + A.bounds.size.height / 2)
    左下方位 = (A.bounds.origin.x, A.bounds.origin.y + A.bounds.size.height)
    中上方位 = (A.bounds.origin.x + A.bounds.size.width/2, A.bounds.origin.y)
    中中方位 = (A.bounds.origin.x + A.bounds.size.width/2, A.bounds.origin.y + A.bounds.size.height/2)
    中下方位 = (A.bounds.origin.x + A.bounds.size.width/2, A.bounds.origin.y + A.bounds.size.height)
    右上方位 = (A.bounds.origin.x + A.bounds.size.width, A.bounds.origin.y)
    右中方位 = (A.bounds.origin.x + A.bounds.size.width,A.bounds.origin.y + A.bounds.size.height/2)
    右下方位 = (A.bounds.origin.x + A.bounds.size.width,A.bounds.origin.y + A.bounds.size.height)
    

    对于位置定义来说 TangramKit 中的**TGLayoutPos类就是一个对位置进行建模的类。 TGLayoutPos 类同时支持采用父视图作为参考系和以兄弟视图作为参考系的定位方式,这可以通过为其中的 equal 方法设置不同类型的值来决定其定位方式。为了实现视图定位我们也为 UIView 扩展出了 3 个水平方位的属性:tg_left, tg_centerX,tg_right来表示左中右三个方位对象。 3 垂直方位的属性:tg_top, tg_centerY,tg_bottom**来表示上、中、下三个方位。这 6 个方位对象将比原生的center属性提供更加强大和丰富的位置定位能力。

    iOS 系统的原生布局体系里面是通过bounds属性和center属性来进行视图的尺寸设置和位置设置的。 bounds 用来指定视图内的左上角方位的坐标值,以及视图的尺寸,而 center 则用来指定视图的中心点方位在父视图这个坐标体系里面的坐标值。为了简化设置 UIView 提供了一个简易的属性frame可以用来直接设置一个视图的尺寸和位置,frame 中的 origin 部分指定视图左上角方位在父视图坐标系里面的坐标值,而 size 部分则指定了视图本身的尺寸frame属性并不是一个实体属性而是一个计算类型的属性,在我们没有对视图进行坐标变换时(视图的 transform 未设置时)我们可以得到如下的frame属性的伪代码实现:

    public var frame:CGRect
    {
       get {
           let x = self.center.x  - self.bounds.size.width / 2
           let y = self.center.y  - self.bounds.size.height / 2
           let width = self.bounds.size.width
           let height = self.bounds.size.height
           return CGRect(x:x, y:y, width:width, height:height) 
      }
      set {
           self.center = CGPoint(x:newValue.origin.x  +  newValue.size.width / 2, y: newValue.origin.y +  newValue.size.height / 2)
           self.bounds.size  = newValue.size
      }
    }
    
    

    综上所述,我们可以看出,所谓视图布局的核心,就是确定一个视图的尺寸,和确定视图在参考视图坐标系里面的坐标位置。为了灵活处理和计算,视图的尺寸可以设置为绝对值类型,也可以设置为相对值类型,也可以设置为特殊的包裹或者填充值类型;视图的位置则可以指定视图中的任意的方位,以及设置这个方位的点在窗口坐标系或者父视图坐标系或者兄弟坐标系中的坐标值。正是提供的这些多样的设置方式,我们就可以在不同的场景中使用不同的设置来完成各种复杂界面的布局。

    ##TangramKit 布局框架 在您不了解 TangramKit 之前,可以先通过下面一个例子来感受和体验一下 TangramKit 的布局构建语法:

    • 有一个容器视图 S 的宽度是 100 而高度则等于由四个从上到下依次排列的子视图 A,B,C,D 的高度总和。
    • 子视图 A 的左边距占用父视图宽度的 20%,而右边距则占用父视图宽度的 30%,高度则等于自身的宽度。
    • 子视图 B 的左边距是 40 ,宽度则占用父视图的剩余宽度,高度是 40 。
    • 子视图 C 的宽度占用父视图的所有宽度,高度是 40 。
    • 子视图 D 的右边距是 20 ,宽度是父视图宽度的 50%,高度是 40 。

    演示效果图

    代码实现如下:

        let S = TGLinearLayout(.vert)
        S.tg_vspace = 10
        S.tg_width.equal(100)
        S.tg_height.equal(.wrap)
    
        let A = UIView()
        A.tg_left.equal(20%)
        A.tg_right.equal(30%)
        A.tg_height.equal(A.tg_width)
        S.addSubview(A)
    
        let B = UIView()
        B.tg_left.equal(40)
        B.tg_width.equal(.fill)
        B.tg_height.equal(40)
        S.addSubview(B)
    
        let C = UIView()
        C.tg_width.equal(.fill)
        C.tg_height.equal(40)
        S.addSubview(C)
    
        let D = UIView()
        D.tg_right.equal(20)
        D.tg_width.equal(50%)
        D.tg_height.equal(40)
        S.addSubview(D)
    
    

    因为 TangramKit 对布局位置类和布局尺寸类的方法重载了运算符:~=、>=、<=、+=、-=、*=、/= 所以您可以用更加简洁的代码进行编写:

        let S = TGLinearLayout(.vert)
        S.tg_vspace = 10
        S.tg_width ~=100
        S.tg_height ~=.wrap
    
        let A = UIView()
        A.tg_left ~=20%
        A.tg_right ~=30%
        A.tg_height ~=A.tg_width
        S.addSubview(A)
    
        let B = UIView()
        B.tg_left ~=40
        B.tg_width ~=.fill
        B.tg_height ~=40
        S.addSubview(B)
    
        let C = UIView()
        C.tg_width ~=.fill
        C.tg_height ~=40
        S.addSubview(C)
    
        let D = UIView()
        D.tg_right ~=20
        D.tg_width ~=50%
        D.tg_height ~=40
        S.addSubview(D)
    

    通过上面的代码,您可以看出用 TangramKit 实现的布局代码和上面场景描述文本几乎相同,非常的利于阅读和理解。那么这些系统又是如何实现的呢?

    ###实现原理 我们知道在对任何一个视图进行布局时,最终都是通过设置视图的尺寸和视图的位置来完成的。在 iOS 中我们可以通过 UIView 的bounds属性来完成视图的尺寸设置,而通过center属性来完成视图的位置设置。为了进行简单的操作,系统提供了frame这个属性来简化对尺寸和位置的设置。这个过程不管是原始的方法还是后续的 AutoLayout 其实现的最终机制都是一致的。每当一个视图的尺寸改变或者要求重新布局时,系统都会调用视图的方法:

    open func layoutSubviews()
    

    而我们可以在 UIView 的派生类中重载上面的方法来实现对这个视图里面的所有子视图的重新布局,至于如何布局子视图则是需要根据应用场景而定。在编程时我们经常会用到一些视图,这种视图只是负责将里面的子视图按照某种规则进行排列和布局,而别无其他的作用。因此我们称这种视图为容器视图或者称为布局视图。 TangramKit 框架对种视图进行了建模而提供了一个从 UIView 派生的布局视图基类TGBaseLayout。这个类的作用就是专门负责对加入到其中的所有子视图进行布局排列,它是通过重载layoutSubviews 方法来完成这个工作的。刚才我们说过如何排列容器视图中的子视图是要根据具体的应用场景而定, 比如有可能是所有子视图从上往下按照添加的顺序依次排列,或者子视图按照某种约束依赖关系来进行布局排列,或者子视图需要多行多列的排列等等。因此我们对常见的布局应用场景进行了抽象,通过建立不同的 TGBaseLayout 的派生类来实现不同的布局处理:

    • 线性布局 TGLinearLayout :线性布局里面的所有子视图都按照添加的顺序依次从上到下或者依次从左到右进行排列。根据排列的方向可以分为垂直线性布局和水平线性布局。线性布局和 iOS9 上的 UIStackView 以及 Android 中的线性布局 LinearLayout 提供一样的功能。

    • 框架布局 TGFrameLayout: 框架布局里面的所有子视图布局时和添加的顺序无关,而是按照设定的位置停靠在布局视图的:左上、左中、左下、中上、中中、中下、右上、右中、右下、填充这个 10 个方位中的任何一个位置上。框架布局里面的子视图只跟框架布局视图的边界建立约束关系。框架布局和 Android 中的框架布局 FrameLayout 提供一样的功能。

    • 表格布局 TGTableLayout :表格布局里面的子视图可以进行多行多列的排列。在使用时要先添加行,然后再在行里面添加列,每行的列数可以随意确定。因为表格布局是线性布局 TGLinearLayout 的派生类,所以表格布局也分为垂直表格布局和水平表格布局。垂直表格布局中的行是从上到下,而列则是从左到右排列;水平表格布局中的行是从左到右,而列是从上到下排列的。表格布局和 Android 中的表格布局 TableLayout 以及 HTML 中的 table,tr,td 元素提供一样的功能。

    • 相对布局 TGRelativeLayout: 相对布局里面的子视图和添加的顺序无关,而是按照子视图之间设定的尺寸约束依赖和位置约束依赖进行布局排列。因此相对布局里面的所有子视图都要设置位置和尺寸的约束和依赖关系。相对布局和 iOS 的 AutoLayout 以及 Android 中的相对布局 RelativeLayout 提供一样的功能。

    • 流式布局 TGFlowLayout: 流式布局里面的子视图按照添加的顺序依次从某个方向排列,而当遇到了这个方向上的排列数量限制或者容器的尺寸限制后将会另起一行,而重新按照原先的方向依次排列。最终这个布局中的子视图将形成多行多列的排列展示。流式布局和线性布局的区别是,线性布局只是单行或者单列的,而流式布局则是多行多列。流式布局和表格布局的区别是,表格布局有明确行的概念,在使用前要添加行再添加列,而流式布局则没有明确行的概念,由布局自动生成行和列。根据排列的方向和限制的规则,流式布局分为垂直数量约束布局、垂直内容约束布局、水平数量约束布局、水平内容约束布局四种布局。流式布局实现了 HTML/CSS3 中的 flex-box 的子集的功能。

    • 浮动布局 TGFloatLayout :浮动布局里面的子视图按照添加的顺序,并且按照每个子视图自身设定的浮动规则向某个方向进行浮动停靠。当子视图的尺寸无法容纳到布局视图的剩余空间时,则会自动寻找一个能够容纳自身尺寸的最佳位置进行浮动停靠。浮动布局里面的子视图并不是有规则的多行多列的排列。根据子视图可以浮动的方向浮动布局分为垂直浮动布局和水平浮动布局。浮动布局和 HTML/CSS 中的 float 定位实现了相同的功能。

    • 路径布局 TGPathLayout: 路径布局里面的子视图按照一个提供的数学函数得到的曲线路径等距离的根据添加的顺序依次排列。所有的子视图的位置都是根据函数曲线中距离相等的点而确定的。路径布局提供了直角坐标系、参数方式、极坐标系三种曲线的构建方法。路径布局是 TangramKit 中的独有的一种布局。

    上述的 7 个派生类分别的实现了大部分的不同的应用场景。在每个派生类的layoutSubviews的实现中都按照描述的规则来设置子视图的尺寸bounds和位置center属性。也就是说最终的子视图的尺寸和位置是在布局视图中的layoutSubviews中进行设置的。那么我们就必须要提供另外一套子视图的布局尺寸和布局位置的设置方法,以便在布局视图布局时将子视图设置好的布局尺寸和布局位置转化为真实的视图尺寸和视图位置。为此 TangramKit 专门提供了一个视图的布局尺寸类TGLayoutSize用来进行子视图的布局尺寸的设置,一个视图的布局位置类TGLayoutPos用来进行子视图的布局位置的设置。我们对 UIView 建立了一个 extension 。分别扩展出了 2 个布局尺寸对象和 6 个布局位置对象:

    
    extension UIView
    {
      
       //左边位置
      var tg_left:TGLayoutPos{get}
       //上边位置   
      var tg_top:TGLayoutPos{get}
       //右边位置    
      var tg_right:TGLayoutPos{get}
       //下边位置    
      var tg_bottom:TGLayoutPos{get}
       //水平中心点位置    
      var tg_centerX:TGLayoutPos{get}
       //垂直中心点位置    
      var tg_centerY:TGLayoutPos{get}
        
      //宽度尺寸
      var tg_width:TGLayoutSize{get}
      //高度尺寸    
      var tg_height:TGLayoutSize{get}
    }
    
    

    也就是说我们将不再直接设置子视图的boundscenter(这两个属性只会在布局视图中的layoutSubviews中设置)属性了,而是直接操作 UIView 扩展出来的布局位置对象和布局尺寸对象。如果把布局视图的layoutSubviews比作一个数学函数的话,那么我们就能得到如下的方程式:

    UIView.center = TGXXXLayout.layoutSubviews(UIView.tg_left, UIView.tg_top, UIView.tg_right, UIView.tg_bottom,UIView.tg_centerX,UIView.tg_centerY)
    
    UIView.bounds = TGXXXLayout.layoutSubviews(UIView.tg_width, UIView.tg_height)
    

    因此我们可以看出不同的 TGBaseLayout 的派生类因为里面的布局方法不相同,而导致子视图的位置和尺寸的计算方法不同,从而得到了我们想要的效果。那么为什么要用 6 个布局位置对象和 2 个布局尺寸对象来设置子视图的位置和尺寸而不直接用boundscenter呢? 原因在于 bounds 和 center 只提供了有限的设置方法而布局位置对象和布局尺寸对象则提供了功能更加强大的设置方法,而这些方法又可以简化我们的编程,以及可以很方便的适配各种不同尺寸的屏幕。(还记得我们上面的例子里面,尺寸和位置可以设置为数值,.wrap, .fill,以及百分比的值吗?)。

    TangramKit 为了存储这些扩展的布局位置和布局尺寸对象,内部是使用了 objc 的 runtime 机制提供的动态属性创建的方法:

    public func objc_getAssociatedObject(_ object: Any!, _ key: UnsafeRawPointer!) -> Any!
    

    系统通过这个方法来关联视图对象的那 6 个布局位置和 2 个布局尺寸对象。

    上面的代码中我们看到了布局容器视图通过layoutSubviews方法来实现对子视图的重新布局。而且也提到了当容器视图的尺寸发生变化时也会激发对layoutSubviews的调用。除了自动激发外,我们可以通过手动调用布局视图的setNeedLayout方法来实现布局视图的layoutSubviews调用。当我们在设置子视图的布局位置和布局尺寸时,系统内部会在设置完成后调用布局视图的setNeedLayout的方法,因此只要对子视图的布局位置和布局尺寸进行设置都会重新激发布局视图的布局视图。那么对子视图的 frame,bounds,center 真实位置和尺寸的改变呢?我们也要激发布局视图的重新布局。为了解决这个问题,我们引入了KVO的机制。布局视图在添加子视图时会监听加入到其中的子视图的 frame,bounds,center 的变化,并在其变化时调用布局视图的setNeedLayout来激发布局视图的重新布局。我们知道每次当一个视图调用 addSubview 添加子视图时都会激发调用者的方法:didAddSubview。为了实现对子视图的变化的监控,布局视图重载了这个方法并对子视图的isHidden,frame,center进行监控:

     override open func didAddSubview(_ subview: UIView) {
            super.didAddSubview(subview)
            
            subview.addObserver(self, forKeyPath:"isHidden", options: NSKeyValueObservingOptions.new, context: nil)
            subview.addObserver(self, forKeyPath:"frame", options: NSKeyValueObservingOptions.new, context: nil)
            subview.addObserver(self, forKeyPath:"center", options: NSKeyValueObservingOptions.new, context: nil)
    
        }
        
        override open func willRemoveSubview(_ subview: UIView) {
            super.willRemoveSubview(subview)        
            subview.removeObserver(self, forKeyPath: "isHidden")
            subview.removeObserver(self, forKeyPath: "frame")
            subview.removeObserver(self, forKeyPath: "center")
    
        }
    
    

    当子视图的 frame 或者 center 变更时,将会激发布局视图的重新布局。上面曾经说过,在布局视图重新布局子视图时最终会调整子视图的 bounds 和 center.那么这样就有可能会形成循环的重新布局,为了解决这种循环递归的情况,布局视图在 layoutSubviews 调用进行布局前设置了一个布局中的标志,而在所有子视图布局完成后将恢复这个布局中的标志。因此当我们布局视图通过 KVO 监控到子视图的位置和尺寸变化时,则会判断那个布局中的标志,如果当前是在布局中则不会再次激发布局视图的重新布局,从而防止了死循环的发生。

    这就是 TangramKit 布局实现的原理,下面的图表列出了 TangramKit 的整个布局框架的类体系结构:

    TangramKit 布局框架体系架构

    ###布局位置类和布局尺寸类 在前面的介绍布局核心的章节以及布局实现原理的章节里面我们有说道布局位置类和布局尺寸类。之所以系统不直接操作视图的bounds 和 center属性而是通过扩展视图的 2 个布局尺寸属性和 6 个布局位置属性来进行子视图的布局设置。原因是后者能够提供丰富和多样的设置。而且我们在编程时也不再需要通过设置视图的 frame 来实现布局了,即使设置也可能会失效。

    ####比重类 TGWeight TGWeight 类的值表示尺寸或者位置的大小是父布局视图的尺寸或者剩余空间的尺寸的比例值,也就是说值的大小依赖于父布局视图的尺寸或者剩余空间的尺寸的大小而确定,这样子视图就不需要明确的指定位置和尺寸的大小了,非常适合那些需要适配屏幕的尺寸和位置的场景。 至于是父视图的尺寸还是父视图剩余空间的尺寸则要根据其所在的布局视图的上下文而确定。比如:

    //假如 A,b 是在一个垂直线性布局下的子视图
    A.tg_width.equal(TGWeight(20))   //A 的宽度是父布局视图宽度的 20%
    A.tg_height.equal(TGWeight(30))  //A 的高度是父布局视图剩余高度的 30%
    B.tg_left.equal(TGWeight(40))  //B 的左边距是父视图宽度的 40%
    B.tg_top.equal(TGWeight(10))  //B 的顶部间距时父视图的剩余高度的 10%
    

    为了简化和更加直观的表示比重类型的值,我们重载%运算符,这样上面的代码就可以简写为如下更加直观的方式:

    //假如 A 是在一个垂直线性布局下的子视图
    A.tg_width.equal(20%)   //A 的宽度是父布局视图宽度的 20%
    A.tg_height.equal(30%)  //A 的高度是父布局视图剩余高度的 30%
    B.tg_left.equal(40%)  //B 的左边距是父视图宽度的 40%
    B.tg_top.equal(10%)  //B 的顶部间距时父视图的剩余高度的 10%
    

    下面的列表中列出了在各种布局下视图的尺寸和位置的 TGWeight 类型值所代表的意义:

    为了表示方便,我们把:

    • 线性布局简称 L
      • 垂直线性布局简称为 LV
      • 水平线性布局简称为 LH
    • 框架布局简称为 FR
    • 垂直表格布局简称为 TV
    • 水平表格布局简称为 TH
    • 相对布局简称为 R
    • 浮动布局简称 FO
    • 流式布局 FL
    • 路径布局简称 P
    • 布局视图的非布局父视图 S
    • 所有布局简称 ALL

    ####布局尺寸类 TGLayoutSize 布局尺寸类用来描述视图布局核心中的视图尺寸。我们对 UIView 扩展出了 2 个布局尺寸对象 :

        public var tg_width:TGLayoutSize
        public var tg_height:TGLayoutSize
    

    分别用来实现视图的宽度和高度的布局尺寸设置。在 TGLayoutSize 类中,我们可以通过方法equal来设置视图尺寸的多种类型的值,类中是通过重载 equal 方法来实现多种类型的值的设置的。

    
        public func equal(_ size:CGFloat, increment:CGFloat = 0, multiple:CGFloat = 1) ->TGLayoutSize
        public func equal(_ weight:TGWeight, increment:CGFloat = 0, multiple:CGFloat = 1) ->TGLayoutSize
        public func equal(_ array:[TGLayoutSize], increment:CGFloat = 0, multiple:CGFloat = 1) ->TGLayoutSize
        public func equal(_ view:UIView,increment:CGFloat = 0, multiple:CGFloat = 1) ->TGLayoutSize
        public func equal(_ dime:TGLayoutSize!, increment:CGFloat = 0, multiple:CGFloat = 1) ->TGLayoutSize
    

    上面的方法中我们可以通过 equal 方法来设置:

    • CGFloat 类型的值表示视图的尺寸是一个绝对值类型的尺寸值。比如:
    A.tg_width.equal(100)  //A 的宽度为 100
    A.tg_height.equal(200) //A 的高度为 200
    
    • TGWeight 类型的值表示视图的尺寸是一个依赖于父视图尺寸的相对比例值。(具体见上面 TGWeight 类型值的定义和使用)
    //假如 A 是在一个垂直线性布局下的子视图
    A.tg_width.equal(20%)   //A 的宽度是父布局视图宽度的 20%
    A.tg_height.equal(30%)  //A 的高度是父布局视图剩余高度的 30%
    
    • TGLayoutSize 类型的值表示视图的尺寸和另外一个尺寸对象的值相等,这也是一种相对值类型的尺寸值,通过设置这种尺寸的依赖我们就可以不必要明确的指定一个具体的值,而是会随着所以依赖的尺寸变化而变化。设置为 TGLayoutSize 类型的值通常用于在相对布局中的子视图,当然也可以在其他类型的布局中使用。下面是一个展示的例子:
      A.tg_width.equal(B.tg_width)  //A 的宽度等于 B 的宽度
      A.tg_height.equal(A.tg_width)  //A 的高度等于 A 的宽度
    展开全文
  • SnapKit ...// ViewController.swift // SK_SnapKit // // Created by coder on 2019/3/6. // Copyright © 2019 AlexanderYeah. All rights reserved. // import UIKit import SnapKit ...

    SnapKit

    1 安装

    SnapKit github地址

    2 文档地址
    在线文档

    //
    //  ViewController.swift
    //  SK_SnapKit
    //
    //  Created by coder on 2019/3/6.
    //  Copyright © 2019 AlexanderYeah. All rights reserved.
    //
    
    import UIKit
    import SnapKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            
            // 1 创建一个View 居中在当前视图
            let viewA = UIView();
            viewA.backgroundColor = UIColor.cyan;
            self.view.addSubview(viewA);
            viewA.snp.makeConstraints { (make) in
                make.width.equalTo(250);
                make.height.equalTo(250);
                make.center.equalTo(self.view.center);
    
            };
            
            // 更为简洁的语法
            viewA.snp.makeConstraints { (make) in
                // 宽高相等的操作
                make.width.height.equalTo(250);
                // 对于父视图居中操作
                make.center.equalToSuperview();
            }
            
            // 2 相对父视图Offset
            let viewB = UIView();
            viewB.backgroundColor = UIColor.orange;
            viewA.addSubview(viewB);
            viewB.snp.makeConstraints { (make) in
                make.bottom.equalToSuperview().offset(-50);
                make.left.equalToSuperview().offset(50);
                make.width.height.equalTo(50);
            };
            
            
            
    
            // 3 竖直水平线相等操作
            // lessThanOrEqualTo <= 操作
            let viewC = UIView();
            viewC.backgroundColor = UIColor.blue;
            viewA.addSubview(viewC);
            viewC.snp.makeConstraints { (make) in
                make.centerX.lessThanOrEqualTo(viewB.snp.left);
                make.top.equalToSuperview().offset(20);
                make.width.height.equalTo(60);
                
            }
            
            // 4 优先级设置(优先级不能大于1000,大于1000会crash)
            // 优先级高的有效执行代码
            let viewD = UIView();
            viewD.backgroundColor = UIColor.purple;
            viewA.addSubview(viewD);
            viewD.snp.makeConstraints { (make) in
                make.width.equalTo(30).priority(100);
                make.width.equalTo(60).priority(200);
                make.height.equalTo(40);
                make.right.equalToSuperview().offset(-20);
                make.top.equalToSuperview().offset(10);
            }
            
            
            
            
            
            
            
            
            
            
        }
    
    
    }
    
    
    
    展开全文
  • 总结一下2016年公司中开发中常用的Swift和iOS开源框架 1:SDWebImage,UIImageView+WebCache加载一张图片。 2:UIViewExt用于定位坐标很有用,可以直接拿到bottom,top,left,right. 3:CustomCatagory,是个类目,...
  • 一、Swift - 自动布局库SnapKit的使用详解1(配置、使用方法、样例) 为了适应各种屏幕尺寸,iOS 6后引入了自动布局(Auto Layout)的概念,通过使用各种Constraint(约束)来实现页面自适应弹性布局。(想了解更...
  • Benefits of the Top Server-Side Swift Frameworks 作者:Ryan Collins 译者:Rocky Wei 【CSDN 有奖征稿啦】技术之路,共同进步,欢迎投稿、给文章纠错,请发送邮件至mobilehub@csdn.net,或加微信 tree-...
  • GitHub IOS 框架 Top 100 简介本文原文链接https://github.com/Aufree/trip-to-iOS/blob/master/Top-100.md主要对当前 GitHub 排名前 100 的项目做一个简单的简介, 方便初学者快速了解到当前 Objective-C 在 GitHub ...
  • 10Swift 开源项目

    2019-07-10 13:52:23
    [译]过去一个月最
  • 1.iina A few of the Features: Based on mpv, which provides the best decoding capacity on macOS Designed for modern macOS (10.10+), aims to offer the best user experience All the features you nee...
  • OC有masonry,swift有SnapKit。熟悉masonry的同学上手SnapKit非常快,因为语法很像。这里记录一下常用的几个方法:先创建一个红色view:1234let redView = UIView()self.view.addSubview(redView) redView....
  • UIKit框架-基础控件Swift版本: 4.UIImageView方法\属性详解
  • 我在github上收集了最好的开源Swift框架,这些框架将帮助您在2019年加快移动应用程序的开发速度。 有时,最好使用第三方框架而不是重新发明轮子,但是您首先要提出一些重要问题: 我真的需要第三方库吗? 我应该...
  • SBLayout是基于Swift4 封装系统的约束而成的单文件约束框架,总代码700多行,支持iOS9及其以上版本,一行代码可以搞定任意难度的AutoLayout约束 使用方法如下: 实例: let redView = UIView() re
  • 官方网址:http://snapkit.io/ Github: ... SnapKit is a DSL to make Auto Layout easy on both iOS and OS X. Simple &amp; Expressive chaining DSL allows building constraints...
  • scrollview.sy_header = TextHeader(normalText: "12", pullingText: "222", refreshingText: "333", orientation: .top, height: 60, font: UIFont.systemFont(ofSize: 14), color: UIColor.black, completion: { ...
  • Top 10 Swift的第三方库

    2016-04-20 15:49:30
    1.AlamofireAlamofire是一个Http网络请求库,构建在NSURLSession和基础URL加载系统之上,它用简单优雅的接口很好的封装了网络请求。// Making a GET request Alamofire.request(.GET, ... .respons
  • // WaterfallLayout.swift // WaterfallSwift // // Created by admin on 2019/10/11. // Copyright © 2019 admin. All rights reserved. // import UIKit /// 瀑布流代理 @objc protocol WaterfallLay...
1 2 3 4 5 ... 20
收藏数 2,159
精华内容 863
热门标签