├── .gitignore ├── .slather.yml ├── .swift-version ├── .travis.yml ├── Demo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── about.imageset │ │ ├── Contents.json │ │ ├── about-1.png │ │ └── about.png │ ├── activityLog.imageset │ │ ├── Contents.json │ │ ├── activityLog-1.png │ │ └── activityLog.png │ ├── avatar.imageset │ │ ├── Contents.json │ │ ├── flyinghd-1.jpg │ │ └── flyinghd.jpg │ ├── banner.imageset │ │ ├── Contents.json │ │ ├── banner-1.jpg │ │ └── banner.jpg │ ├── camera.imageset │ │ ├── Contents.json │ │ ├── camera-1.png │ │ └── camera.png │ ├── friends.imageset │ │ ├── Contents.json │ │ ├── friends-1.png │ │ └── friends.png │ ├── more.imageset │ │ ├── Contents.json │ │ ├── more-1.png │ │ └── more.png │ ├── photos.imageset │ │ ├── Contents.json │ │ ├── photos-1.jpg │ │ └── photos.jpg │ ├── post.imageset │ │ ├── Contents.json │ │ ├── post-1.png │ │ └── post.png │ ├── searchBar.imageset │ │ ├── Contents.json │ │ ├── searchBar-1.png │ │ └── searchBar.png │ └── updateInfo.imageset │ │ ├── Contents.json │ │ ├── updateInfo-1.png │ │ └── updateInfo.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── FacebookProfileExampleViewController.swift ├── IconButton.swift ├── ImageContainerView.swift ├── Info.plist ├── TestViewController.swift └── ViewController.swift ├── LICENSE.txt ├── Neon.podspec ├── Neon.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Neon Demo.xcscheme │ ├── Neon OSX.xcscheme │ ├── Neon iOS.xcscheme │ └── NeonUnitTests.xcscheme ├── NeonUnitTests ├── Info.plist └── NeonUnitTests.swift ├── README.md ├── Screenshots ├── align.png ├── align_between_fill.png ├── align_fill.png ├── align_offset.png ├── auto_height_1.png ├── auto_height_2.png ├── center.png ├── corner.png ├── demo.gif ├── edge.png ├── fill.png ├── fill_edge.png ├── group_against_edge.png ├── group_and_fill.png ├── group_in_center.png ├── group_in_corner.png ├── group_relative.png ├── landscape.png ├── logo.png ├── portrait.png └── side_by_side.png └── Source ├── Info.plist ├── Neon.h ├── Neon.swift ├── NeonAlignable.swift ├── NeonAnchorable.swift ├── NeonExtensions.swift ├── NeonFrameable.swift └── NeonGroupable.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: coveralls 2 | xcodeproj: Neon.xcodeproj 3 | scheme: NeonUnitTests 4 | source_directory: Source 5 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode8 3 | before_install: gem install cocoapods xcpretty obcd slather -N 4 | xcode_sdk: iphonesimulator10.0 5 | xcode_project: Neon.xcodeproj 6 | xcode_scheme: NeonUnitTests 7 | after_success: slather 8 | -------------------------------------------------------------------------------- /Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/16/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func applicationDidFinishLaunching(_ application: UIApplication) { 16 | self.window = UIWindow.init(frame: UIScreen.main.bounds) 17 | self.window?.backgroundColor = UIColor.white 18 | 19 | // The facebook example from the README.... Don't ask why the file name is FacebookProfileExampleViewController 20 | // but the class referenced here is TwitterProfileExampleViewController... I didn't realize you can't yet refactor 21 | // swift code, and somehow this still works... 22 | UINavigationBar.appearance().barTintColor = UIColor(red: 80/255.0, green: 108/255.0, blue: 163/255.0, alpha: 1.0) 23 | UINavigationBar.appearance().isTranslucent = false 24 | // self.window?.rootViewController = UINavigationController(rootViewController: TwitterProfileExampleViewController()) 25 | 26 | // Used for tinkering and testing new methods - uncomment this out if you want to experiment! 27 | // self.window?.rootViewController = TestViewController() 28 | 29 | // This is crazy-complex demo I have on the README. Uncomment this out to experience with that as well, 30 | // although I really built it for iPads. 31 | self.window?.rootViewController = ViewController() 32 | 33 | self.window?.makeKeyAndVisible() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/about.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "about-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "about.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/about.imageset/about-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/about.imageset/about-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/about.imageset/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/about.imageset/about.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/activityLog.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "activityLog-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "activityLog.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/activityLog.imageset/activityLog-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/activityLog.imageset/activityLog-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/activityLog.imageset/activityLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/activityLog.imageset/activityLog.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/avatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "flyinghd-1.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "flyinghd.jpg", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/avatar.imageset/flyinghd-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/avatar.imageset/flyinghd-1.jpg -------------------------------------------------------------------------------- /Demo/Assets.xcassets/avatar.imageset/flyinghd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/avatar.imageset/flyinghd.jpg -------------------------------------------------------------------------------- /Demo/Assets.xcassets/banner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "banner-1.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "banner.jpg", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/banner.imageset/banner-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/banner.imageset/banner-1.jpg -------------------------------------------------------------------------------- /Demo/Assets.xcassets/banner.imageset/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/banner.imageset/banner.jpg -------------------------------------------------------------------------------- /Demo/Assets.xcassets/camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "camera-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "camera.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/camera.imageset/camera-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/camera.imageset/camera-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/camera.imageset/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/camera.imageset/camera.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/friends.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "friends-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "friends.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/friends.imageset/friends-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/friends.imageset/friends-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/friends.imageset/friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/friends.imageset/friends.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "more-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "more.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/more.imageset/more-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/more.imageset/more-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/more.imageset/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/more.imageset/more.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/photos.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "photos-1.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "photos.jpg", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/photos.imageset/photos-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/photos.imageset/photos-1.jpg -------------------------------------------------------------------------------- /Demo/Assets.xcassets/photos.imageset/photos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/photos.imageset/photos.jpg -------------------------------------------------------------------------------- /Demo/Assets.xcassets/post.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "post-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "post.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/post.imageset/post-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/post.imageset/post-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/post.imageset/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/post.imageset/post.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/searchBar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "searchBar-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "searchBar.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/searchBar.imageset/searchBar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/searchBar.imageset/searchBar-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/searchBar.imageset/searchBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/searchBar.imageset/searchBar.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/updateInfo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "updateInfo-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "updateInfo.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Demo/Assets.xcassets/updateInfo.imageset/updateInfo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/updateInfo.imageset/updateInfo-1.png -------------------------------------------------------------------------------- /Demo/Assets.xcassets/updateInfo.imageset/updateInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Demo/Assets.xcassets/updateInfo.imageset/updateInfo.png -------------------------------------------------------------------------------- /Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Demo/FacebookProfileExampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FacebookProfileExampleViewController.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/26/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Neon 11 | 12 | 13 | class TwitterProfileExampleViewController: UIViewController { 14 | let searchBar : UIImageView = UIImageView() 15 | let bannerImageView : UIImageView = UIImageView() 16 | let bannerMaskView : UIView = UIView() 17 | let avatarImageView : UIImageView = UIImageView() 18 | let nameLabel : UILabel = UILabel() 19 | let cameraButton : UIButton = UIButton(type: .custom) 20 | 21 | let buttonContainerView : UIView = UIView() 22 | let postButton : IconButton = IconButton() 23 | let updateInfoButton : IconButton = IconButton() 24 | let activityLogButton : IconButton = IconButton() 25 | let moreButton : IconButton = IconButton() 26 | 27 | let buttonContainerView2 : UIView = UIView() 28 | let aboutView : ImageContainerView = ImageContainerView() 29 | let photosView : ImageContainerView = ImageContainerView() 30 | let friendsView : ImageContainerView = ImageContainerView() 31 | 32 | convenience init() { 33 | self.init(nibName:nil, bundle:nil) 34 | 35 | // So I cheated here... shhhh! 36 | // (can't take all damn day making this match perfectly!) 37 | searchBar.image = UIImage(named: "searchBar") 38 | searchBar.contentMode = .scaleAspectFit 39 | 40 | bannerImageView.image = UIImage(named: "banner") 41 | bannerImageView.contentMode = .scaleAspectFill 42 | bannerImageView.clipsToBounds = true 43 | 44 | bannerMaskView.backgroundColor = UIColor(white: 0.0, alpha: 0.2) 45 | 46 | avatarImageView.image = UIImage(named: "avatar") 47 | avatarImageView.layer.cornerRadius = 1.0 48 | avatarImageView.layer.borderColor = UIColor.white.cgColor 49 | avatarImageView.layer.borderWidth = 2.0 50 | avatarImageView.clipsToBounds = true 51 | 52 | cameraButton.setImage(UIImage(named: "camera"), for: .normal) 53 | 54 | nameLabel.textColor = UIColor.white 55 | nameLabel.text = "Mike\nAmaral" 56 | nameLabel.numberOfLines = 2 57 | nameLabel.font = UIFont(name: "HelveticaNeue-Light", size: 33) 58 | 59 | buttonContainerView.backgroundColor = UIColor.white 60 | buttonContainerView.layer.shadowColor = UIColor.black.cgColor 61 | buttonContainerView.layer.shadowOffset = CGSize(width: 0, height: 0.5) 62 | buttonContainerView.layer.shadowOpacity = 0.4 63 | 64 | postButton.label.text = "Post" 65 | postButton.imageView.image = UIImage(named: "post") 66 | 67 | updateInfoButton.label.text = "Update Info" 68 | updateInfoButton.imageView.image = UIImage(named: "updateInfo") 69 | 70 | activityLogButton.label.text = "Activity Log" 71 | activityLogButton.imageView.image = UIImage(named: "activityLog") 72 | 73 | moreButton.label.text = "More" 74 | moreButton.imageView.image = UIImage(named: "more") 75 | 76 | buttonContainerView2.backgroundColor = UIColor.clear 77 | 78 | aboutView.imageView.image = UIImage(named: "about") 79 | aboutView.label.text = "About" 80 | 81 | photosView.imageView.image = UIImage(named: "photos") 82 | photosView.label.text = "Photos" 83 | 84 | friendsView.imageView.image = UIImage(named: "friends") 85 | friendsView.label.text = "Friends" 86 | } 87 | 88 | override func viewDidLoad() { 89 | super.viewDidLoad() 90 | 91 | navigationItem.titleView = searchBar 92 | 93 | view.backgroundColor = UIColor(red: 211/255.0, green: 214/255.0, blue: 219/255.0, alpha: 1.0) 94 | 95 | view.addSubview(bannerImageView) 96 | bannerImageView.addSubview(bannerMaskView) 97 | bannerImageView.addSubview(cameraButton) 98 | bannerImageView.addSubview(avatarImageView) 99 | bannerImageView.addSubview(nameLabel) 100 | 101 | view.addSubview(buttonContainerView) 102 | buttonContainerView.addSubview(postButton) 103 | buttonContainerView.addSubview(updateInfoButton) 104 | buttonContainerView.addSubview(activityLogButton) 105 | buttonContainerView.addSubview(moreButton) 106 | 107 | view.addSubview(buttonContainerView2) 108 | buttonContainerView2.addSubview(aboutView) 109 | buttonContainerView2.addSubview(photosView) 110 | buttonContainerView2.addSubview(friendsView) 111 | } 112 | 113 | override func viewWillLayoutSubviews() { 114 | super.viewWillLayoutSubviews() 115 | 116 | layoutFrames() 117 | } 118 | 119 | func layoutFrames() { 120 | let isLandscape : Bool = UIDevice.current.orientation.isLandscape 121 | let bannerHeight : CGFloat = view.height * 0.465 122 | let avatarHeightMultipler : CGFloat = isLandscape ? 0.75 : 0.43 123 | let avatarSize = bannerHeight * avatarHeightMultipler 124 | 125 | searchBar.fillSuperview() 126 | bannerImageView.anchorAndFillEdge(.top, xPad: 0, yPad: 0, otherSize: bannerHeight) 127 | bannerMaskView.fillSuperview() 128 | avatarImageView.anchorInCorner(.bottomLeft, xPad: 15, yPad: 15, width: avatarSize, height: avatarSize) 129 | nameLabel.alignAndFillWidth(align: .toTheRightCentered, relativeTo: avatarImageView, padding: 15, height: 120) 130 | cameraButton.anchorInCorner(.bottomRight, xPad: 10, yPad: 7, width: 28, height: 28) 131 | buttonContainerView.alignAndFillWidth(align: .underCentered, relativeTo: bannerImageView, padding: 0, height: 62) 132 | buttonContainerView.groupAndFill(group: .horizontal, views: [postButton, updateInfoButton, activityLogButton, moreButton], padding: 10) 133 | buttonContainerView2.alignAndFillWidth(align: .underCentered, relativeTo: buttonContainerView, padding: 0, height: 120) 134 | buttonContainerView2.groupAndFill(group: .horizontal, views: [aboutView, photosView, friendsView], padding: 8) 135 | } 136 | } 137 | 138 | -------------------------------------------------------------------------------- /Demo/IconButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconButton.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/26/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Neon 11 | 12 | 13 | class IconButton: UIView { 14 | let imageView : UIImageView = UIImageView() 15 | let label : UILabel = UILabel() 16 | 17 | convenience init() { 18 | self.init(frame: CGRect.zero) 19 | 20 | imageView.contentMode = .scaleAspectFill 21 | self.addSubview(imageView) 22 | 23 | label.textAlignment = .center 24 | label.textColor = UIColor(red: 106/255.0, green: 113/255.0, blue: 127/255.0, alpha: 1.0) 25 | label.font = UIFont.systemFont(ofSize: 13.0) 26 | self.addSubview(label) 27 | } 28 | 29 | override func layoutSubviews() { 30 | super.layoutSubviews() 31 | 32 | imageView.anchorToEdge(.top, padding: 0, width: 24, height: 24) 33 | label.align(.underCentered, relativeTo: imageView, padding: 5, width: self.width, height: 15) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Demo/ImageContainerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageContainerView.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/26/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Neon 11 | 12 | class ImageContainerView: UIView { 13 | let imageView : UIImageView = UIImageView() 14 | let label : UILabel = UILabel() 15 | 16 | convenience init() { 17 | self.init(frame: CGRect.zero) 18 | 19 | self.backgroundColor = UIColor.white 20 | self.layer.cornerRadius = 4.0 21 | self.layer.borderWidth = 1.0 22 | self.layer.borderColor = UIColor(white: 0.68, alpha: 1.0).cgColor 23 | self.clipsToBounds = true 24 | 25 | imageView.contentMode = .scaleAspectFill 26 | imageView.clipsToBounds = true 27 | self.addSubview(imageView) 28 | 29 | label.textAlignment = .center 30 | label.textColor = UIColor.black 31 | label.font = UIFont.boldSystemFont(ofSize: 14.0) 32 | self.addSubview(label) 33 | } 34 | 35 | override func layoutSubviews() { 36 | super.layoutSubviews() 37 | 38 | imageView.anchorAndFillEdge(.top, xPad: 0, yPad: 0, otherSize: self.height * 0.7) 39 | label.alignAndFill(align: .underCentered, relativeTo: imageView, padding: 0) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarStyle 32 | UIStatusBarStyleLightContent 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Demo/TestViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestViewController.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/20/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Neon 11 | 12 | class TestViewController: UIViewController { 13 | let anchorLayer : CALayer = CALayer() 14 | let anchorViewA : UILabel = UILabel() 15 | let anchorViewB : UILabel = UILabel() 16 | let anchorViewC : UILabel = UILabel() 17 | let anchorViewD : UILabel = UILabel() 18 | let view1 : UILabel = UILabel() 19 | let view2 : UILabel = UILabel() 20 | let view3 : UILabel = UILabel() 21 | let view4 : UILabel = UILabel() 22 | let view5 : UILabel = UILabel() 23 | let view6 : UILabel = UILabel() 24 | let testLabel : UILabel = UILabel() 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | anchorLayer.backgroundColor = UIColor.red.cgColor 30 | view.layer.addSublayer(anchorLayer) 31 | 32 | anchorViewA.backgroundColor = UIColor(red: 229/255.0, green: 72/255.0, blue: 26/255.0, alpha: 1.0) 33 | anchorViewA.text = "" 34 | anchorViewA.textAlignment = .center 35 | anchorViewA.font = UIFont.boldSystemFont(ofSize: 20) 36 | anchorViewA.textColor = UIColor.white 37 | view.addSubview(anchorViewA) 38 | 39 | anchorViewB.backgroundColor = UIColor(red: 229/255.0, green: 72/255.0, blue: 26/255.0, alpha: 1.0) 40 | anchorViewB.text = "" 41 | anchorViewB.textAlignment = .center 42 | anchorViewB.font = UIFont.boldSystemFont(ofSize: 20) 43 | anchorViewB.textColor = UIColor.white 44 | view.addSubview(anchorViewB) 45 | 46 | anchorViewC.backgroundColor = UIColor(red: 229/255.0, green: 72/255.0, blue: 26/255.0, alpha: 1.0) 47 | anchorViewC.text = "C" 48 | anchorViewC.textAlignment = .center 49 | anchorViewC.font = UIFont.boldSystemFont(ofSize: 20) 50 | anchorViewC.textColor = UIColor.white 51 | view.addSubview(anchorViewC) 52 | 53 | anchorViewD.backgroundColor = UIColor(red: 229/255.0, green: 72/255.0, blue: 26/255.0, alpha: 1.0) 54 | anchorViewD.text = "D" 55 | anchorViewD.textAlignment = .center 56 | anchorViewD.font = UIFont.boldSystemFont(ofSize: 20) 57 | anchorViewD.textColor = UIColor.white 58 | view.addSubview(anchorViewD) 59 | 60 | view1.backgroundColor = UIColor(red: 78/255.0, green: 102/255.0, blue: 131/255.0, alpha: 1.0) 61 | view1.text = "1" 62 | view1.textAlignment = .center 63 | view1.font = UIFont.boldSystemFont(ofSize: 20) 64 | view1.textColor = UIColor.white 65 | view.addSubview(view1) 66 | 67 | view2.backgroundColor = UIColor(red: 132/255.0, green: 169/255.0, blue: 57/255.0, alpha: 1.0) 68 | view2.text = "2" 69 | view2.textAlignment = .center 70 | view2.font = UIFont.boldSystemFont(ofSize: 20) 71 | view2.textColor = UIColor.white 72 | view.addSubview(view2) 73 | 74 | view3.backgroundColor = UIColor(red: 78/255.0, green: 102/255.0, blue: 131/255.0, alpha: 1.0) 75 | view3.text = "3" 76 | view3.textAlignment = .center 77 | view3.font = UIFont.boldSystemFont(ofSize: 20) 78 | view3.textColor = UIColor.white 79 | view.addSubview(view3) 80 | 81 | view4.backgroundColor = UIColor(red: 132/255.0, green: 169/255.0, blue: 57/255.0, alpha: 1.0) 82 | view4.text = "4" 83 | view4.textAlignment = .center 84 | view4.font = UIFont.boldSystemFont(ofSize: 20) 85 | view4.textColor = UIColor.white 86 | view.addSubview(view4) 87 | 88 | view5.backgroundColor = UIColor(red: 78/255.0, green: 102/255.0, blue: 131/255.0, alpha: 1.0) 89 | view5.text = "5" 90 | view5.textAlignment = .center 91 | view5.font = UIFont.boldSystemFont(ofSize: 20) 92 | view5.textColor = UIColor.white 93 | view.addSubview(view5) 94 | 95 | view6.backgroundColor = UIColor(red: 132/255.0, green: 169/255.0, blue: 57/255.0, alpha: 1.0) 96 | view6.text = "6" 97 | view6.textAlignment = .center 98 | view6.font = UIFont.boldSystemFont(ofSize: 20) 99 | view6.textColor = UIColor.white 100 | view.addSubview(view6) 101 | 102 | testLabel.backgroundColor = UIColor(red: 132/255.0, green: 169/255.0, blue: 57/255.0, alpha: 1.0) 103 | testLabel.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 104 | testLabel.textAlignment = .center 105 | testLabel.font = UIFont.boldSystemFont(ofSize: 14) 106 | testLabel.textColor = UIColor.white 107 | testLabel.numberOfLines = 0 108 | view.addSubview(testLabel) 109 | } 110 | 111 | override func viewWillLayoutSubviews() { 112 | super.viewWillLayoutSubviews() 113 | 114 | anchorViewA.anchorInCorner(.topLeft, xPad: 20, yPad: 20, width: 200, height: 200) 115 | view1.align(.toTheRightMatchingTop, relativeTo: anchorViewA, padding: 20, width: 100, height: 100, offset: 20) 116 | view2.align(.underMatchingLeft, relativeTo: anchorViewA, padding: 20, width: 100, height: 100, offset: 20) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/16/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Neon 11 | 12 | 13 | class ViewController: UIViewController { 14 | let containerView : UIView = UIView() 15 | let anchorView : UIView = UIView() 16 | let view1 : UILabel = UILabel() 17 | let view2 : UILabel = UILabel() 18 | let view3 : UILabel = UILabel() 19 | let view4 : UILabel = UILabel() 20 | let view5 : UILabel = UILabel() 21 | let view6 : UILabel = UILabel() 22 | let view7 : UILabel = UILabel() 23 | let view8 : UILabel = UILabel() 24 | let view9 : UILabel = UILabel() 25 | let view10 : UILabel = UILabel() 26 | let view11 : UILabel = UILabel() 27 | let view12 : UILabel = UILabel() 28 | let view13 : UILabel = UILabel() 29 | let view14 : UILabel = UILabel() 30 | let view15 : UILabel = UILabel() 31 | let view16 : UILabel = UILabel() 32 | let view17 : UILabel = UILabel() 33 | let view18 : UILabel = UILabel() 34 | let view19 : UILabel = UILabel() 35 | let view20 : UILabel = UILabel() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | containerView.clipsToBounds = true 41 | containerView.backgroundColor = UIColor(red: 61/255.0, green: 61/255.0, blue: 61/255.0, alpha: 1.0) 42 | view.addSubview(containerView) 43 | 44 | anchorView.backgroundColor = UIColor(red: 229/255.0, green: 72/255.0, blue: 26/255.0, alpha: 1.0) 45 | containerView.addSubview(anchorView) 46 | 47 | view1.backgroundColor = UIColor(red: 78/255.0, green: 102/255.0, blue: 131/255.0, alpha: 1.0) 48 | view1.text = "1" 49 | view1.textAlignment = .center 50 | view1.font = UIFont.boldSystemFont(ofSize: 20) 51 | view1.textColor = UIColor.white 52 | containerView.addSubview(view1) 53 | 54 | view2.backgroundColor = UIColor(red: 132/255.0, green: 169/255.0, blue: 57/255.0, alpha: 1.0) 55 | view2.text = "2" 56 | view2.textAlignment = .center 57 | view2.font = UIFont.boldSystemFont(ofSize: 20) 58 | view2.textColor = UIColor.white 59 | containerView.addSubview(view2) 60 | 61 | view3.backgroundColor = UIColor(red: 146/255.0, green: 83/255.0, blue: 72/255.0, alpha: 1.0) 62 | view3.text = "3" 63 | view3.textAlignment = .center 64 | view3.font = UIFont.boldSystemFont(ofSize: 20) 65 | view3.textColor = UIColor.white 66 | containerView.addSubview(view3) 67 | 68 | view4.backgroundColor = UIColor(red: 100/255.0, green: 112/255.0, blue: 108/255.0, alpha: 1.0) 69 | view4.text = "4" 70 | view4.textAlignment = .center 71 | view4.font = UIFont.boldSystemFont(ofSize: 20) 72 | view4.textColor = UIColor.white 73 | containerView.addSubview(view4) 74 | 75 | view5.backgroundColor = UIColor(red: 33/255.0, green: 154/255.0, blue: 209/255.0, alpha: 1.0) 76 | view5.text = "5" 77 | view5.textAlignment = .center 78 | view5.font = UIFont.boldSystemFont(ofSize: 20) 79 | view5.textColor = UIColor.white 80 | containerView.addSubview(view5) 81 | 82 | view6.backgroundColor = UIColor(red: 229/255.0, green: 174/255.0, blue: 84/255.0, alpha: 1.0) 83 | view6.text = "6" 84 | view6.textAlignment = .center 85 | view6.font = UIFont.boldSystemFont(ofSize: 20) 86 | view6.textColor = UIColor.white 87 | containerView.addSubview(view6) 88 | 89 | view7.backgroundColor = UIColor(red: 222/255.0, green: 81/255.0, blue: 62/255.0, alpha: 1.0) 90 | view7.text = "7" 91 | view7.textAlignment = .center 92 | view7.font = UIFont.boldSystemFont(ofSize: 20) 93 | view7.textColor = UIColor.white 94 | containerView.addSubview(view7) 95 | 96 | view8.backgroundColor = UIColor(red: 198/255.0, green: 173/255.0, blue: 138/255.0, alpha: 1.0) 97 | view8.text = "8" 98 | view8.textAlignment = .center 99 | view8.font = UIFont.boldSystemFont(ofSize: 20) 100 | view8.textColor = UIColor.white 101 | containerView.addSubview(view8) 102 | 103 | view9.backgroundColor = UIColor(red: 157/255.0, green: 104/255.0, blue: 80/255.0, alpha: 1.0) 104 | view9.text = "9" 105 | view9.textAlignment = .center 106 | view9.font = UIFont.boldSystemFont(ofSize: 20) 107 | view9.textColor = UIColor.white 108 | containerView.addSubview(view9) 109 | 110 | view10.backgroundColor = UIColor(red: 23/255.0, green: 59/255.0, blue: 140/255.0, alpha: 1.0) 111 | view10.text = "10" 112 | view10.textAlignment = .center 113 | view10.font = UIFont.boldSystemFont(ofSize: 20) 114 | view10.textColor = UIColor.white 115 | containerView.addSubview(view10) 116 | 117 | view11.backgroundColor = UIColor(red: 229/255.0, green: 174/255.0, blue: 84/255.0, alpha: 1.0) 118 | view11.text = "11" 119 | view11.textAlignment = .center 120 | view11.font = UIFont.boldSystemFont(ofSize: 20) 121 | view11.textColor = UIColor.white 122 | anchorView.addSubview(view11) 123 | 124 | view12.backgroundColor = UIColor(red: 33/255.0, green: 154/255.0, blue: 209/255.0, alpha: 1.0) 125 | view12.text = "12" 126 | view12.textAlignment = .center 127 | view12.font = UIFont.boldSystemFont(ofSize: 20) 128 | view12.textColor = UIColor.white 129 | anchorView.addSubview(view12) 130 | 131 | view13.backgroundColor = UIColor(red: 100/255.0, green: 112/255.0, blue: 108/255.0, alpha: 1.0) 132 | view13.text = "13" 133 | view13.textAlignment = .center 134 | view13.font = UIFont.boldSystemFont(ofSize: 20) 135 | view13.textColor = UIColor.white 136 | anchorView.addSubview(view13) 137 | 138 | view14.backgroundColor = UIColor(red: 198/255.0, green: 173/255.0, blue: 138/255.0, alpha: 1.0) 139 | view14.text = "14" 140 | view14.textAlignment = .center 141 | view14.font = UIFont.boldSystemFont(ofSize: 20) 142 | view14.textColor = UIColor.white 143 | containerView.addSubview(view14) 144 | 145 | view15.backgroundColor = UIColor(red: 146/255.0, green: 83/255.0, blue: 72/255.0, alpha: 1.0) 146 | view15.text = "15" 147 | view15.textAlignment = .center 148 | view15.font = UIFont.boldSystemFont(ofSize: 20) 149 | view15.textColor = UIColor.white 150 | containerView.addSubview(view15) 151 | 152 | view16.backgroundColor = UIColor(red: 78/255.0, green: 102/255.0, blue: 131/255.0, alpha: 1.0) 153 | view16.text = "16" 154 | view16.textAlignment = .center 155 | view16.font = UIFont.boldSystemFont(ofSize: 20) 156 | view16.textColor = UIColor.white 157 | containerView.addSubview(view16) 158 | 159 | view17.backgroundColor = UIColor(red: 33/255.0, green: 154/255.0, blue: 209/255.0, alpha: 1.0) 160 | view17.text = "17" 161 | view17.textAlignment = .center 162 | view17.font = UIFont.boldSystemFont(ofSize: 20) 163 | view17.textColor = UIColor.white 164 | containerView.addSubview(view17) 165 | 166 | view18.backgroundColor = UIColor(red: 146/255.0, green: 83/255.0, blue: 72/255.0, alpha: 1.0) 167 | view18.text = "18" 168 | view18.textAlignment = .center 169 | view18.font = UIFont.boldSystemFont(ofSize: 20) 170 | view18.textColor = UIColor.white 171 | containerView.addSubview(view18) 172 | 173 | view19.backgroundColor = UIColor(red: 23/255.0, green: 59/255.0, blue: 140/255.0, alpha: 1.0) 174 | view19.text = "19" 175 | view19.textAlignment = .center 176 | view19.font = UIFont.boldSystemFont(ofSize: 20) 177 | view19.textColor = UIColor.white 178 | containerView.addSubview(view19) 179 | 180 | view20.backgroundColor = UIColor(red: 222/255.0, green: 81/255.0, blue: 62/255.0, alpha: 1.0) 181 | view20.text = "20" 182 | view20.textAlignment = .center 183 | view20.font = UIFont.boldSystemFont(ofSize: 20) 184 | view20.textColor = UIColor.white 185 | containerView.addSubview(view20) 186 | } 187 | 188 | override func viewWillLayoutSubviews() { 189 | super.viewWillLayoutSubviews() 190 | 191 | // Static container for the demo. 192 | // 193 | containerView.fillSuperview(left: 10, right: 10, top: 25, bottom: 10) 194 | anchorView.anchorInCenter(width: 200, height: 200) 195 | 196 | layoutFrames() 197 | } 198 | 199 | func layoutFrames() { 200 | anchorView.groupInCorner(group: .vertical, views: [view11, view12, view13], inCorner: .topRight, padding: 10, width: 40, height: 40) 201 | view1.alignAndFillWidth(align: .toTheRightMatchingTop, relativeTo: anchorView, padding: 10, height: 50) 202 | containerView.groupAndAlign(group: .horizontal, andAlign: .underMatchingLeft, views: [view2, view3, view4], relativeTo: view1, padding: 10, width: 60, height: 60) 203 | view5.alignAndFillWidth(align: .toTheRightMatchingTop, relativeTo: view4, padding: 10, height: 60) 204 | view6.alignAndFill(align: .underMatchingLeft, relativeTo: view2, padding: 10) 205 | view7.alignAndFillHeight(align: .aboveMatchingRight, relativeTo: view1, padding: 10, width: 60) 206 | view8.alignAndFillWidth(align: .toTheLeftMatchingTop, relativeTo: view7, padding: 10, height: 70) 207 | view9.alignBetweenVertical(align: .underMatchingLeft, primaryView: view8, secondaryView: anchorView, padding: 10, width: 100) 208 | view10.alignBetweenHorizontal(align: .toTheRightMatchingTop, primaryView: view9, secondaryView: view7, padding: 10, height: view9.height) 209 | view14.anchorInCorner(.bottomLeft, xPad: 10, yPad: 10, width: 100, height: 100) 210 | view15.alignBetweenVertical(align: .underMatchingLeft, primaryView: view9, secondaryView: view14, padding: 10, width: 50) 211 | view16.alignBetweenHorizontal(align: .toTheRightMatchingBottom, primaryView: view14, secondaryView: view6, padding: 10, height: 40) 212 | view17.alignBetweenHorizontal(align: .toTheRightMatchingTop, primaryView: view15, secondaryView: anchorView, padding: 10, height: 100) 213 | view18.alignBetweenVertical(align: .underMatchingLeft, primaryView: anchorView, secondaryView: view16, padding: 10, width: anchorView.width) 214 | view19.alignBetweenHorizontal(align: .toTheRightMatchingTop, primaryView: view14, secondaryView: view18, padding: 10, height: 50) 215 | view20.alignBetweenVertical(align: .underCentered, primaryView: view17, secondaryView: view19, padding: 10, width: view17.width) 216 | } 217 | 218 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 219 | let touch : UITouch = touches.first! 220 | let point = touch.location(in: containerView) 221 | 222 | anchorView.frame = CGRect(x: point.x - (anchorView.width / 2.0), y: point.y - (anchorView.height / 2.0), width: anchorView.width, height: anchorView.height) 223 | 224 | layoutFrames() 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Michael Amaral 2 | 3 | MIT LICENSE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Neon.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Neon' 3 | s.version = '0.4.0' 4 | s.license = 'MIT' 5 | s.summary = 'A powerful Swift programmatic UI layout framework.' 6 | s.homepage = 'https://github.com/mamaral/neon' 7 | s.social_media_url = 'http://twitter.com/MikeAmaral' 8 | s.authors = { 'Mike Amaral' => 'mike.amaral36@gmail.com' } 9 | s.source = { :git => 'https://github.com/mamaral/neon.git', :tag => 'v0.4.0' } 10 | s.osx.deployment_target = '10.11' 11 | s.ios.deployment_target = '9.0' 12 | s.source_files = 'Source/*.swift' 13 | s.requires_arc = true 14 | end 15 | -------------------------------------------------------------------------------- /Neon.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 47; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 033D78F11BBF900A0034752F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 033D78F01BBF900A0034752F /* Assets.xcassets */; }; 11 | 033D78F81BBF90860034752F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 033D78F61BBF90860034752F /* LaunchScreen.storyboard */; }; 12 | 036A08351BBF919E000B093A /* NeonUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036A08341BBF919E000B093A /* NeonUnitTests.swift */; }; 13 | 036A08371BBF919E000B093A /* Neon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03EDD3831BBF89910006C4A9 /* Neon.framework */; }; 14 | 03EDD38A1BBF89910006C4A9 /* Neon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03EDD3831BBF89910006C4A9 /* Neon.framework */; }; 15 | 03EDD38B1BBF89910006C4A9 /* Neon.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 03EDD3831BBF89910006C4A9 /* Neon.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 03EDD3911BBF89E90006C4A9 /* Neon.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD3901BBF89E90006C4A9 /* Neon.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 03EDD3A11BBF8A820006C4A9 /* Neon.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD3901BBF89E90006C4A9 /* Neon.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 03EDD3B31BBF8BC90006C4A9 /* Neon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3AE1BBF8BC90006C4A9 /* Neon.swift */; }; 19 | 03EDD3B41BBF8BCA0006C4A9 /* Neon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3AE1BBF8BC90006C4A9 /* Neon.swift */; }; 20 | 03EDD3B51BBF8BCA0006C4A9 /* NeonAlignable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3AF1BBF8BC90006C4A9 /* NeonAlignable.swift */; }; 21 | 03EDD3B61BBF8BCA0006C4A9 /* NeonAlignable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3AF1BBF8BC90006C4A9 /* NeonAlignable.swift */; }; 22 | 03EDD3B71BBF8BCA0006C4A9 /* NeonAnchorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3B01BBF8BC90006C4A9 /* NeonAnchorable.swift */; }; 23 | 03EDD3B81BBF8BCA0006C4A9 /* NeonAnchorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3B01BBF8BC90006C4A9 /* NeonAnchorable.swift */; }; 24 | 03EDD3B91BBF8BCA0006C4A9 /* NeonFrameable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3B11BBF8BC90006C4A9 /* NeonFrameable.swift */; }; 25 | 03EDD3BA1BBF8BCA0006C4A9 /* NeonFrameable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3B11BBF8BC90006C4A9 /* NeonFrameable.swift */; }; 26 | 03EDD3BB1BBF8BCA0006C4A9 /* NeonGroupable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3B21BBF8BC90006C4A9 /* NeonGroupable.swift */; }; 27 | 03EDD3BC1BBF8BCA0006C4A9 /* NeonGroupable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3B21BBF8BC90006C4A9 /* NeonGroupable.swift */; }; 28 | 03EDD3C41BBF8CEC0006C4A9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3BD1BBF8CEC0006C4A9 /* AppDelegate.swift */; }; 29 | 03EDD3C51BBF8CEC0006C4A9 /* FacebookProfileExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3BE1BBF8CEC0006C4A9 /* FacebookProfileExampleViewController.swift */; }; 30 | 03EDD3C61BBF8CEC0006C4A9 /* IconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3BF1BBF8CEC0006C4A9 /* IconButton.swift */; }; 31 | 03EDD3C71BBF8CEC0006C4A9 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3C01BBF8CEC0006C4A9 /* ImageContainerView.swift */; }; 32 | 03EDD3C81BBF8CEC0006C4A9 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3C21BBF8CEC0006C4A9 /* TestViewController.swift */; }; 33 | 03EDD3C91BBF8CEC0006C4A9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD3C31BBF8CEC0006C4A9 /* ViewController.swift */; }; 34 | 52E7D6F71D520983007288D6 /* NeonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E7D6F61D520983007288D6 /* NeonExtensions.swift */; }; 35 | 52E7D6F81D520983007288D6 /* NeonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E7D6F61D520983007288D6 /* NeonExtensions.swift */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | 036A08381BBF919E000B093A /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = 27D2535E1BAF71AB00FA4FED /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = 03EDD3821BBF89910006C4A9; 44 | remoteInfo = "Neon-iOS"; 45 | }; 46 | 03EDD3881BBF89910006C4A9 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 27D2535E1BAF71AB00FA4FED /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 03EDD3821BBF89910006C4A9; 51 | remoteInfo = "Neon-iOS"; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXCopyFilesBuildPhase section */ 56 | 03EDD38F1BBF89910006C4A9 /* Embed Frameworks */ = { 57 | isa = PBXCopyFilesBuildPhase; 58 | buildActionMask = 2147483647; 59 | dstPath = ""; 60 | dstSubfolderSpec = 10; 61 | files = ( 62 | 03EDD38B1BBF89910006C4A9 /* Neon.framework in Embed Frameworks */, 63 | ); 64 | name = "Embed Frameworks"; 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXCopyFilesBuildPhase section */ 68 | 69 | /* Begin PBXFileReference section */ 70 | 033D78F01BBF900A0034752F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Demo/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 71 | 033D78F21BBF90140034752F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Demo/Info.plist; sourceTree = SOURCE_ROOT; }; 72 | 033D78F71BBF90860034752F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Demo/Base.lproj/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; }; 73 | 036A08321BBF919E000B093A /* NeonUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NeonUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | 036A08341BBF919E000B093A /* NeonUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeonUnitTests.swift; sourceTree = ""; }; 75 | 036A08361BBF919E000B093A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 76 | 03EDD3831BBF89910006C4A9 /* Neon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Neon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | 03EDD3901BBF89E90006C4A9 /* Neon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Neon.h; path = Source/Neon.h; sourceTree = SOURCE_ROOT; }; 78 | 03EDD3971BBF8A4F0006C4A9 /* Neon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Neon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 03EDD3A01BBF8A7E0006C4A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Source/Info.plist; sourceTree = SOURCE_ROOT; }; 80 | 03EDD3AE1BBF8BC90006C4A9 /* Neon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Neon.swift; path = Source/Neon.swift; sourceTree = SOURCE_ROOT; }; 81 | 03EDD3AF1BBF8BC90006C4A9 /* NeonAlignable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NeonAlignable.swift; path = Source/NeonAlignable.swift; sourceTree = SOURCE_ROOT; }; 82 | 03EDD3B01BBF8BC90006C4A9 /* NeonAnchorable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NeonAnchorable.swift; path = Source/NeonAnchorable.swift; sourceTree = SOURCE_ROOT; }; 83 | 03EDD3B11BBF8BC90006C4A9 /* NeonFrameable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NeonFrameable.swift; path = Source/NeonFrameable.swift; sourceTree = SOURCE_ROOT; }; 84 | 03EDD3B21BBF8BC90006C4A9 /* NeonGroupable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NeonGroupable.swift; path = Source/NeonGroupable.swift; sourceTree = SOURCE_ROOT; }; 85 | 03EDD3BD1BBF8CEC0006C4A9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Demo/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 86 | 03EDD3BE1BBF8CEC0006C4A9 /* FacebookProfileExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FacebookProfileExampleViewController.swift; path = Demo/FacebookProfileExampleViewController.swift; sourceTree = SOURCE_ROOT; }; 87 | 03EDD3BF1BBF8CEC0006C4A9 /* IconButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = IconButton.swift; path = Demo/IconButton.swift; sourceTree = SOURCE_ROOT; }; 88 | 03EDD3C01BBF8CEC0006C4A9 /* ImageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageContainerView.swift; path = Demo/ImageContainerView.swift; sourceTree = SOURCE_ROOT; }; 89 | 03EDD3C21BBF8CEC0006C4A9 /* TestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestViewController.swift; path = Demo/TestViewController.swift; sourceTree = SOURCE_ROOT; }; 90 | 03EDD3C31BBF8CEC0006C4A9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = Demo/ViewController.swift; sourceTree = SOURCE_ROOT; }; 91 | 27D253661BAF71AB00FA4FED /* Neon_Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Neon_Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | 52E7D6F61D520983007288D6 /* NeonExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NeonExtensions.swift; path = Source/NeonExtensions.swift; sourceTree = SOURCE_ROOT; }; 93 | /* End PBXFileReference section */ 94 | 95 | /* Begin PBXFrameworksBuildPhase section */ 96 | 036A082F1BBF919E000B093A /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | 036A08371BBF919E000B093A /* Neon.framework in Frameworks */, 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | 03EDD37F1BBF89910006C4A9 /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | 03EDD3931BBF8A4F0006C4A9 /* Frameworks */ = { 112 | isa = PBXFrameworksBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | ); 116 | runOnlyForDeploymentPostprocessing = 0; 117 | }; 118 | 27D253631BAF71AB00FA4FED /* Frameworks */ = { 119 | isa = PBXFrameworksBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | 03EDD38A1BBF89910006C4A9 /* Neon.framework in Frameworks */, 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXFrameworksBuildPhase section */ 127 | 128 | /* Begin PBXGroup section */ 129 | 036A08331BBF919E000B093A /* NeonUnitTests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 036A08341BBF919E000B093A /* NeonUnitTests.swift */, 133 | 036A08361BBF919E000B093A /* Info.plist */, 134 | ); 135 | path = NeonUnitTests; 136 | sourceTree = ""; 137 | }; 138 | 03EDD37D1BBF89350006C4A9 /* Tests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | ); 142 | name = Tests; 143 | sourceTree = ""; 144 | }; 145 | 03EDD39F1BBF8A6A0006C4A9 /* Supporting Files */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 03EDD3901BBF89E90006C4A9 /* Neon.h */, 149 | 03EDD3A01BBF8A7E0006C4A9 /* Info.plist */, 150 | ); 151 | name = "Supporting Files"; 152 | sourceTree = ""; 153 | }; 154 | 27D2535D1BAF71AB00FA4FED = { 155 | isa = PBXGroup; 156 | children = ( 157 | 27D253971BAF71C700FA4FED /* Source */, 158 | 27D253681BAF71AB00FA4FED /* Demo */, 159 | 036A08331BBF919E000B093A /* NeonUnitTests */, 160 | 27D253671BAF71AB00FA4FED /* Products */, 161 | 03EDD37D1BBF89350006C4A9 /* Tests */, 162 | ); 163 | sourceTree = ""; 164 | }; 165 | 27D253671BAF71AB00FA4FED /* Products */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 27D253661BAF71AB00FA4FED /* Neon_Demo.app */, 169 | 03EDD3831BBF89910006C4A9 /* Neon.framework */, 170 | 03EDD3971BBF8A4F0006C4A9 /* Neon.framework */, 171 | 036A08321BBF919E000B093A /* NeonUnitTests.xctest */, 172 | ); 173 | name = Products; 174 | sourceTree = ""; 175 | }; 176 | 27D253681BAF71AB00FA4FED /* Demo */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 03EDD3BD1BBF8CEC0006C4A9 /* AppDelegate.swift */, 180 | 03EDD3BF1BBF8CEC0006C4A9 /* IconButton.swift */, 181 | 03EDD3C31BBF8CEC0006C4A9 /* ViewController.swift */, 182 | 03EDD3C21BBF8CEC0006C4A9 /* TestViewController.swift */, 183 | 03EDD3C01BBF8CEC0006C4A9 /* ImageContainerView.swift */, 184 | 03EDD3BE1BBF8CEC0006C4A9 /* FacebookProfileExampleViewController.swift */, 185 | 27D253A01BAF735300FA4FED /* Supporting Files */, 186 | ); 187 | name = Demo; 188 | path = Neon; 189 | sourceTree = ""; 190 | }; 191 | 27D253971BAF71C700FA4FED /* Source */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 03EDD3AE1BBF8BC90006C4A9 /* Neon.swift */, 195 | 52E7D6F61D520983007288D6 /* NeonExtensions.swift */, 196 | 03EDD3B11BBF8BC90006C4A9 /* NeonFrameable.swift */, 197 | 03EDD3B01BBF8BC90006C4A9 /* NeonAnchorable.swift */, 198 | 03EDD3AF1BBF8BC90006C4A9 /* NeonAlignable.swift */, 199 | 03EDD3B21BBF8BC90006C4A9 /* NeonGroupable.swift */, 200 | 03EDD39F1BBF8A6A0006C4A9 /* Supporting Files */, 201 | ); 202 | name = Source; 203 | path = ../../Source; 204 | sourceTree = ""; 205 | }; 206 | 27D253A01BAF735300FA4FED /* Supporting Files */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 033D78F61BBF90860034752F /* LaunchScreen.storyboard */, 210 | 033D78F01BBF900A0034752F /* Assets.xcassets */, 211 | 033D78F21BBF90140034752F /* Info.plist */, 212 | ); 213 | name = "Supporting Files"; 214 | sourceTree = ""; 215 | }; 216 | /* End PBXGroup section */ 217 | 218 | /* Begin PBXHeadersBuildPhase section */ 219 | 03EDD3801BBF89910006C4A9 /* Headers */ = { 220 | isa = PBXHeadersBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 03EDD3911BBF89E90006C4A9 /* Neon.h in Headers */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | 03EDD3941BBF8A4F0006C4A9 /* Headers */ = { 228 | isa = PBXHeadersBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | 03EDD3A11BBF8A820006C4A9 /* Neon.h in Headers */, 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | /* End PBXHeadersBuildPhase section */ 236 | 237 | /* Begin PBXNativeTarget section */ 238 | 036A08311BBF919E000B093A /* NeonUnitTests */ = { 239 | isa = PBXNativeTarget; 240 | buildConfigurationList = 036A083A1BBF919E000B093A /* Build configuration list for PBXNativeTarget "NeonUnitTests" */; 241 | buildPhases = ( 242 | 036A082E1BBF919E000B093A /* Sources */, 243 | 036A082F1BBF919E000B093A /* Frameworks */, 244 | 036A08301BBF919E000B093A /* Resources */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | 036A08391BBF919E000B093A /* PBXTargetDependency */, 250 | ); 251 | name = NeonUnitTests; 252 | productName = NeonUnitTests; 253 | productReference = 036A08321BBF919E000B093A /* NeonUnitTests.xctest */; 254 | productType = "com.apple.product-type.bundle.unit-test"; 255 | }; 256 | 03EDD3821BBF89910006C4A9 /* Neon iOS */ = { 257 | isa = PBXNativeTarget; 258 | buildConfigurationList = 03EDD38C1BBF89910006C4A9 /* Build configuration list for PBXNativeTarget "Neon iOS" */; 259 | buildPhases = ( 260 | 03EDD37E1BBF89910006C4A9 /* Sources */, 261 | 03EDD37F1BBF89910006C4A9 /* Frameworks */, 262 | 03EDD3801BBF89910006C4A9 /* Headers */, 263 | 03EDD3811BBF89910006C4A9 /* Resources */, 264 | ); 265 | buildRules = ( 266 | ); 267 | dependencies = ( 268 | ); 269 | name = "Neon iOS"; 270 | productName = "Neon-iOS"; 271 | productReference = 03EDD3831BBF89910006C4A9 /* Neon.framework */; 272 | productType = "com.apple.product-type.framework"; 273 | }; 274 | 03EDD3961BBF8A4F0006C4A9 /* Neon OSX */ = { 275 | isa = PBXNativeTarget; 276 | buildConfigurationList = 03EDD39C1BBF8A4F0006C4A9 /* Build configuration list for PBXNativeTarget "Neon OSX" */; 277 | buildPhases = ( 278 | 03EDD3921BBF8A4F0006C4A9 /* Sources */, 279 | 03EDD3931BBF8A4F0006C4A9 /* Frameworks */, 280 | 03EDD3941BBF8A4F0006C4A9 /* Headers */, 281 | 03EDD3951BBF8A4F0006C4A9 /* Resources */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | ); 287 | name = "Neon OSX"; 288 | productName = "Neon-OSX"; 289 | productReference = 03EDD3971BBF8A4F0006C4A9 /* Neon.framework */; 290 | productType = "com.apple.product-type.framework"; 291 | }; 292 | 27D253651BAF71AB00FA4FED /* Neon Demo */ = { 293 | isa = PBXNativeTarget; 294 | buildConfigurationList = 27D2538E1BAF71AB00FA4FED /* Build configuration list for PBXNativeTarget "Neon Demo" */; 295 | buildPhases = ( 296 | 27D253621BAF71AB00FA4FED /* Sources */, 297 | 27D253631BAF71AB00FA4FED /* Frameworks */, 298 | 27D253641BAF71AB00FA4FED /* Resources */, 299 | 03EDD38F1BBF89910006C4A9 /* Embed Frameworks */, 300 | ); 301 | buildRules = ( 302 | ); 303 | dependencies = ( 304 | 03EDD3891BBF89910006C4A9 /* PBXTargetDependency */, 305 | ); 306 | name = "Neon Demo"; 307 | productName = Neon; 308 | productReference = 27D253661BAF71AB00FA4FED /* Neon_Demo.app */; 309 | productType = "com.apple.product-type.application"; 310 | }; 311 | /* End PBXNativeTarget section */ 312 | 313 | /* Begin PBXProject section */ 314 | 27D2535E1BAF71AB00FA4FED /* Project object */ = { 315 | isa = PBXProject; 316 | attributes = { 317 | LastSwiftUpdateCheck = 0710; 318 | LastUpgradeCheck = 0900; 319 | ORGANIZATIONNAME = "Mike Amaral"; 320 | TargetAttributes = { 321 | 036A08311BBF919E000B093A = { 322 | CreatedOnToolsVersion = 7.1; 323 | LastSwiftMigration = 0800; 324 | }; 325 | 03EDD3821BBF89910006C4A9 = { 326 | CreatedOnToolsVersion = 7.1; 327 | LastSwiftMigration = 0900; 328 | }; 329 | 03EDD3961BBF8A4F0006C4A9 = { 330 | CreatedOnToolsVersion = 7.1; 331 | LastSwiftMigration = 0800; 332 | }; 333 | 27D253651BAF71AB00FA4FED = { 334 | CreatedOnToolsVersion = 7.0; 335 | DevelopmentTeam = 9W3UPW3F39; 336 | LastSwiftMigration = 0900; 337 | ProvisioningStyle = Automatic; 338 | }; 339 | }; 340 | }; 341 | buildConfigurationList = 27D253611BAF71AB00FA4FED /* Build configuration list for PBXProject "Neon" */; 342 | compatibilityVersion = "Xcode 6.3"; 343 | developmentRegion = English; 344 | hasScannedForEncodings = 0; 345 | knownRegions = ( 346 | en, 347 | Base, 348 | ); 349 | mainGroup = 27D2535D1BAF71AB00FA4FED; 350 | productRefGroup = 27D253671BAF71AB00FA4FED /* Products */; 351 | projectDirPath = ""; 352 | projectRoot = ""; 353 | targets = ( 354 | 03EDD3821BBF89910006C4A9 /* Neon iOS */, 355 | 03EDD3961BBF8A4F0006C4A9 /* Neon OSX */, 356 | 27D253651BAF71AB00FA4FED /* Neon Demo */, 357 | 036A08311BBF919E000B093A /* NeonUnitTests */, 358 | ); 359 | }; 360 | /* End PBXProject section */ 361 | 362 | /* Begin PBXResourcesBuildPhase section */ 363 | 036A08301BBF919E000B093A /* Resources */ = { 364 | isa = PBXResourcesBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | ); 368 | runOnlyForDeploymentPostprocessing = 0; 369 | }; 370 | 03EDD3811BBF89910006C4A9 /* Resources */ = { 371 | isa = PBXResourcesBuildPhase; 372 | buildActionMask = 2147483647; 373 | files = ( 374 | ); 375 | runOnlyForDeploymentPostprocessing = 0; 376 | }; 377 | 03EDD3951BBF8A4F0006C4A9 /* Resources */ = { 378 | isa = PBXResourcesBuildPhase; 379 | buildActionMask = 2147483647; 380 | files = ( 381 | ); 382 | runOnlyForDeploymentPostprocessing = 0; 383 | }; 384 | 27D253641BAF71AB00FA4FED /* Resources */ = { 385 | isa = PBXResourcesBuildPhase; 386 | buildActionMask = 2147483647; 387 | files = ( 388 | 033D78F11BBF900A0034752F /* Assets.xcassets in Resources */, 389 | 033D78F81BBF90860034752F /* LaunchScreen.storyboard in Resources */, 390 | ); 391 | runOnlyForDeploymentPostprocessing = 0; 392 | }; 393 | /* End PBXResourcesBuildPhase section */ 394 | 395 | /* Begin PBXSourcesBuildPhase section */ 396 | 036A082E1BBF919E000B093A /* Sources */ = { 397 | isa = PBXSourcesBuildPhase; 398 | buildActionMask = 2147483647; 399 | files = ( 400 | 036A08351BBF919E000B093A /* NeonUnitTests.swift in Sources */, 401 | ); 402 | runOnlyForDeploymentPostprocessing = 0; 403 | }; 404 | 03EDD37E1BBF89910006C4A9 /* Sources */ = { 405 | isa = PBXSourcesBuildPhase; 406 | buildActionMask = 2147483647; 407 | files = ( 408 | 03EDD3B31BBF8BC90006C4A9 /* Neon.swift in Sources */, 409 | 52E7D6F71D520983007288D6 /* NeonExtensions.swift in Sources */, 410 | 03EDD3BB1BBF8BCA0006C4A9 /* NeonGroupable.swift in Sources */, 411 | 03EDD3B91BBF8BCA0006C4A9 /* NeonFrameable.swift in Sources */, 412 | 03EDD3B51BBF8BCA0006C4A9 /* NeonAlignable.swift in Sources */, 413 | 03EDD3B71BBF8BCA0006C4A9 /* NeonAnchorable.swift in Sources */, 414 | ); 415 | runOnlyForDeploymentPostprocessing = 0; 416 | }; 417 | 03EDD3921BBF8A4F0006C4A9 /* Sources */ = { 418 | isa = PBXSourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | 03EDD3B41BBF8BCA0006C4A9 /* Neon.swift in Sources */, 422 | 52E7D6F81D520983007288D6 /* NeonExtensions.swift in Sources */, 423 | 03EDD3BC1BBF8BCA0006C4A9 /* NeonGroupable.swift in Sources */, 424 | 03EDD3BA1BBF8BCA0006C4A9 /* NeonFrameable.swift in Sources */, 425 | 03EDD3B61BBF8BCA0006C4A9 /* NeonAlignable.swift in Sources */, 426 | 03EDD3B81BBF8BCA0006C4A9 /* NeonAnchorable.swift in Sources */, 427 | ); 428 | runOnlyForDeploymentPostprocessing = 0; 429 | }; 430 | 27D253621BAF71AB00FA4FED /* Sources */ = { 431 | isa = PBXSourcesBuildPhase; 432 | buildActionMask = 2147483647; 433 | files = ( 434 | 03EDD3C91BBF8CEC0006C4A9 /* ViewController.swift in Sources */, 435 | 03EDD3C81BBF8CEC0006C4A9 /* TestViewController.swift in Sources */, 436 | 03EDD3C71BBF8CEC0006C4A9 /* ImageContainerView.swift in Sources */, 437 | 03EDD3C61BBF8CEC0006C4A9 /* IconButton.swift in Sources */, 438 | 03EDD3C41BBF8CEC0006C4A9 /* AppDelegate.swift in Sources */, 439 | 03EDD3C51BBF8CEC0006C4A9 /* FacebookProfileExampleViewController.swift in Sources */, 440 | ); 441 | runOnlyForDeploymentPostprocessing = 0; 442 | }; 443 | /* End PBXSourcesBuildPhase section */ 444 | 445 | /* Begin PBXTargetDependency section */ 446 | 036A08391BBF919E000B093A /* PBXTargetDependency */ = { 447 | isa = PBXTargetDependency; 448 | target = 03EDD3821BBF89910006C4A9 /* Neon iOS */; 449 | targetProxy = 036A08381BBF919E000B093A /* PBXContainerItemProxy */; 450 | }; 451 | 03EDD3891BBF89910006C4A9 /* PBXTargetDependency */ = { 452 | isa = PBXTargetDependency; 453 | target = 03EDD3821BBF89910006C4A9 /* Neon iOS */; 454 | targetProxy = 03EDD3881BBF89910006C4A9 /* PBXContainerItemProxy */; 455 | }; 456 | /* End PBXTargetDependency section */ 457 | 458 | /* Begin PBXVariantGroup section */ 459 | 033D78F61BBF90860034752F /* LaunchScreen.storyboard */ = { 460 | isa = PBXVariantGroup; 461 | children = ( 462 | 033D78F71BBF90860034752F /* Base */, 463 | ); 464 | name = LaunchScreen.storyboard; 465 | sourceTree = ""; 466 | }; 467 | /* End PBXVariantGroup section */ 468 | 469 | /* Begin XCBuildConfiguration section */ 470 | 036A083B1BBF919E000B093A /* Debug */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 474 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 475 | INFOPLIST_FILE = NeonUnitTests/Info.plist; 476 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = com.extremelylimited.NeonUnitTests; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SWIFT_VERSION = 3.0; 481 | }; 482 | name = Debug; 483 | }; 484 | 036A083C1BBF919E000B093A /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 488 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 489 | INFOPLIST_FILE = NeonUnitTests/Info.plist; 490 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 491 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 492 | PRODUCT_BUNDLE_IDENTIFIER = com.extremelylimited.NeonUnitTests; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | SWIFT_VERSION = 3.0; 495 | }; 496 | name = Release; 497 | }; 498 | 03EDD38D1BBF89910006C4A9 /* Debug */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | CLANG_ENABLE_MODULES = YES; 502 | CODE_SIGN_IDENTITY = ""; 503 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 504 | CURRENT_PROJECT_VERSION = 1; 505 | DEFINES_MODULE = YES; 506 | DYLIB_COMPATIBILITY_VERSION = 1; 507 | DYLIB_CURRENT_VERSION = 1; 508 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 509 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 510 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 511 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; 512 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 513 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 514 | OTHER_LDFLAGS = ( 515 | "-D", 516 | DEBUG, 517 | ); 518 | PRODUCT_BUNDLE_IDENTIFIER = com.MikeAmaral.NeoniOS; 519 | PRODUCT_NAME = Neon; 520 | SKIP_INSTALL = YES; 521 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 522 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 523 | SWIFT_VERSION = 4.0; 524 | VERSIONING_SYSTEM = "apple-generic"; 525 | VERSION_INFO_PREFIX = ""; 526 | }; 527 | name = Debug; 528 | }; 529 | 03EDD38E1BBF89910006C4A9 /* Release */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | CLANG_ENABLE_MODULES = YES; 533 | CODE_SIGN_IDENTITY = ""; 534 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 535 | CURRENT_PROJECT_VERSION = 1; 536 | DEFINES_MODULE = YES; 537 | DYLIB_COMPATIBILITY_VERSION = 1; 538 | DYLIB_CURRENT_VERSION = 1; 539 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 540 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 541 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 542 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; 543 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 544 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 545 | OTHER_LDFLAGS = ( 546 | "-D", 547 | DEBUG, 548 | ); 549 | PRODUCT_BUNDLE_IDENTIFIER = com.MikeAmaral.NeoniOS; 550 | PRODUCT_NAME = Neon; 551 | SKIP_INSTALL = YES; 552 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 553 | SWIFT_VERSION = 4.0; 554 | VERSIONING_SYSTEM = "apple-generic"; 555 | VERSION_INFO_PREFIX = ""; 556 | }; 557 | name = Release; 558 | }; 559 | 03EDD39D1BBF8A4F0006C4A9 /* Debug */ = { 560 | isa = XCBuildConfiguration; 561 | buildSettings = { 562 | CLANG_ENABLE_MODULES = YES; 563 | CODE_SIGN_IDENTITY = ""; 564 | COMBINE_HIDPI_IMAGES = YES; 565 | CURRENT_PROJECT_VERSION = 1; 566 | DEFINES_MODULE = YES; 567 | DYLIB_COMPATIBILITY_VERSION = 1; 568 | DYLIB_CURRENT_VERSION = 1; 569 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 570 | FRAMEWORK_VERSION = A; 571 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 572 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 573 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; 574 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 575 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 576 | MACOSX_DEPLOYMENT_TARGET = 10.9; 577 | PRODUCT_BUNDLE_IDENTIFIER = com.MikeAmaral.NeonOSX; 578 | PRODUCT_NAME = Neon; 579 | SDKROOT = macosx; 580 | SKIP_INSTALL = YES; 581 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 582 | SWIFT_VERSION = 4.0; 583 | VERSIONING_SYSTEM = "apple-generic"; 584 | VERSION_INFO_PREFIX = ""; 585 | }; 586 | name = Debug; 587 | }; 588 | 03EDD39E1BBF8A4F0006C4A9 /* Release */ = { 589 | isa = XCBuildConfiguration; 590 | buildSettings = { 591 | CLANG_ENABLE_MODULES = YES; 592 | CODE_SIGN_IDENTITY = ""; 593 | COMBINE_HIDPI_IMAGES = YES; 594 | CURRENT_PROJECT_VERSION = 1; 595 | DEFINES_MODULE = YES; 596 | DYLIB_COMPATIBILITY_VERSION = 1; 597 | DYLIB_CURRENT_VERSION = 1; 598 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 599 | FRAMEWORK_VERSION = A; 600 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 601 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 602 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; 603 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 604 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 605 | MACOSX_DEPLOYMENT_TARGET = 10.9; 606 | PRODUCT_BUNDLE_IDENTIFIER = com.MikeAmaral.NeonOSX; 607 | PRODUCT_NAME = Neon; 608 | SDKROOT = macosx; 609 | SKIP_INSTALL = YES; 610 | SWIFT_VERSION = 4.0; 611 | VERSIONING_SYSTEM = "apple-generic"; 612 | VERSION_INFO_PREFIX = ""; 613 | }; 614 | name = Release; 615 | }; 616 | 27D2538C1BAF71AB00FA4FED /* Debug */ = { 617 | isa = XCBuildConfiguration; 618 | buildSettings = { 619 | ALWAYS_SEARCH_USER_PATHS = NO; 620 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 621 | CLANG_CXX_LIBRARY = "libc++"; 622 | CLANG_ENABLE_MODULES = YES; 623 | CLANG_ENABLE_OBJC_ARC = YES; 624 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 625 | CLANG_WARN_BOOL_CONVERSION = YES; 626 | CLANG_WARN_COMMA = YES; 627 | CLANG_WARN_CONSTANT_CONVERSION = YES; 628 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 629 | CLANG_WARN_EMPTY_BODY = YES; 630 | CLANG_WARN_ENUM_CONVERSION = YES; 631 | CLANG_WARN_INFINITE_RECURSION = YES; 632 | CLANG_WARN_INT_CONVERSION = YES; 633 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 634 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 635 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 636 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 637 | CLANG_WARN_STRICT_PROTOTYPES = YES; 638 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 639 | CLANG_WARN_UNREACHABLE_CODE = YES; 640 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 641 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 642 | COPY_PHASE_STRIP = NO; 643 | DEBUG_INFORMATION_FORMAT = dwarf; 644 | ENABLE_STRICT_OBJC_MSGSEND = YES; 645 | ENABLE_TESTABILITY = YES; 646 | GCC_C_LANGUAGE_STANDARD = gnu99; 647 | GCC_DYNAMIC_NO_PIC = NO; 648 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 649 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 650 | GCC_NO_COMMON_BLOCKS = YES; 651 | GCC_OPTIMIZATION_LEVEL = 0; 652 | GCC_PREPROCESSOR_DEFINITIONS = ( 653 | "DEBUG=1", 654 | "$(inherited)", 655 | ); 656 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 657 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 658 | GCC_WARN_UNDECLARED_SELECTOR = YES; 659 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 660 | GCC_WARN_UNUSED_FUNCTION = YES; 661 | GCC_WARN_UNUSED_VARIABLE = YES; 662 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 663 | MACOSX_DEPLOYMENT_TARGET = 10.9; 664 | MTL_ENABLE_DEBUG_INFO = YES; 665 | ONLY_ACTIVE_ARCH = YES; 666 | SDKROOT = iphoneos; 667 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 668 | TARGETED_DEVICE_FAMILY = "1,2"; 669 | }; 670 | name = Debug; 671 | }; 672 | 27D2538D1BAF71AB00FA4FED /* Release */ = { 673 | isa = XCBuildConfiguration; 674 | buildSettings = { 675 | ALWAYS_SEARCH_USER_PATHS = NO; 676 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 677 | CLANG_CXX_LIBRARY = "libc++"; 678 | CLANG_ENABLE_MODULES = YES; 679 | CLANG_ENABLE_OBJC_ARC = YES; 680 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 681 | CLANG_WARN_BOOL_CONVERSION = YES; 682 | CLANG_WARN_COMMA = YES; 683 | CLANG_WARN_CONSTANT_CONVERSION = YES; 684 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 685 | CLANG_WARN_EMPTY_BODY = YES; 686 | CLANG_WARN_ENUM_CONVERSION = YES; 687 | CLANG_WARN_INFINITE_RECURSION = YES; 688 | CLANG_WARN_INT_CONVERSION = YES; 689 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 690 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 691 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 692 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 693 | CLANG_WARN_STRICT_PROTOTYPES = YES; 694 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 695 | CLANG_WARN_UNREACHABLE_CODE = YES; 696 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 697 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 698 | COPY_PHASE_STRIP = NO; 699 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 700 | ENABLE_NS_ASSERTIONS = NO; 701 | ENABLE_STRICT_OBJC_MSGSEND = YES; 702 | GCC_C_LANGUAGE_STANDARD = gnu99; 703 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 704 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 705 | GCC_NO_COMMON_BLOCKS = YES; 706 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 707 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 708 | GCC_WARN_UNDECLARED_SELECTOR = YES; 709 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 710 | GCC_WARN_UNUSED_FUNCTION = YES; 711 | GCC_WARN_UNUSED_VARIABLE = YES; 712 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 713 | MACOSX_DEPLOYMENT_TARGET = 10.9; 714 | MTL_ENABLE_DEBUG_INFO = NO; 715 | SDKROOT = iphoneos; 716 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 717 | TARGETED_DEVICE_FAMILY = "1,2"; 718 | VALIDATE_PRODUCT = YES; 719 | }; 720 | name = Release; 721 | }; 722 | 27D2538F1BAF71AB00FA4FED /* Debug */ = { 723 | isa = XCBuildConfiguration; 724 | buildSettings = { 725 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 726 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 727 | CLANG_ENABLE_MODULES = YES; 728 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 729 | DEVELOPMENT_TEAM = 9W3UPW3F39; 730 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 731 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 732 | INFOPLIST_FILE = "$(SRCROOT)/Demo/Info.plist"; 733 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 734 | PRODUCT_BUNDLE_IDENTIFIER = com.MikeAmaral.NeonDemo; 735 | PRODUCT_MODULE_NAME = Neon_Demo; 736 | PRODUCT_NAME = Neon_Demo; 737 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 738 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 739 | SWIFT_VERSION = 4.0; 740 | }; 741 | name = Debug; 742 | }; 743 | 27D253901BAF71AB00FA4FED /* Release */ = { 744 | isa = XCBuildConfiguration; 745 | buildSettings = { 746 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 747 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 748 | CLANG_ENABLE_MODULES = YES; 749 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 750 | DEVELOPMENT_TEAM = 9W3UPW3F39; 751 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 752 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; 753 | INFOPLIST_FILE = "$(SRCROOT)/Demo/Info.plist"; 754 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 755 | PRODUCT_BUNDLE_IDENTIFIER = com.MikeAmaral.NeonDemo; 756 | PRODUCT_MODULE_NAME = Neon_Demo; 757 | PRODUCT_NAME = Neon_Demo; 758 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 759 | SWIFT_VERSION = 4.0; 760 | }; 761 | name = Release; 762 | }; 763 | /* End XCBuildConfiguration section */ 764 | 765 | /* Begin XCConfigurationList section */ 766 | 036A083A1BBF919E000B093A /* Build configuration list for PBXNativeTarget "NeonUnitTests" */ = { 767 | isa = XCConfigurationList; 768 | buildConfigurations = ( 769 | 036A083B1BBF919E000B093A /* Debug */, 770 | 036A083C1BBF919E000B093A /* Release */, 771 | ); 772 | defaultConfigurationIsVisible = 0; 773 | defaultConfigurationName = Release; 774 | }; 775 | 03EDD38C1BBF89910006C4A9 /* Build configuration list for PBXNativeTarget "Neon iOS" */ = { 776 | isa = XCConfigurationList; 777 | buildConfigurations = ( 778 | 03EDD38D1BBF89910006C4A9 /* Debug */, 779 | 03EDD38E1BBF89910006C4A9 /* Release */, 780 | ); 781 | defaultConfigurationIsVisible = 0; 782 | defaultConfigurationName = Release; 783 | }; 784 | 03EDD39C1BBF8A4F0006C4A9 /* Build configuration list for PBXNativeTarget "Neon OSX" */ = { 785 | isa = XCConfigurationList; 786 | buildConfigurations = ( 787 | 03EDD39D1BBF8A4F0006C4A9 /* Debug */, 788 | 03EDD39E1BBF8A4F0006C4A9 /* Release */, 789 | ); 790 | defaultConfigurationIsVisible = 0; 791 | defaultConfigurationName = Release; 792 | }; 793 | 27D253611BAF71AB00FA4FED /* Build configuration list for PBXProject "Neon" */ = { 794 | isa = XCConfigurationList; 795 | buildConfigurations = ( 796 | 27D2538C1BAF71AB00FA4FED /* Debug */, 797 | 27D2538D1BAF71AB00FA4FED /* Release */, 798 | ); 799 | defaultConfigurationIsVisible = 0; 800 | defaultConfigurationName = Release; 801 | }; 802 | 27D2538E1BAF71AB00FA4FED /* Build configuration list for PBXNativeTarget "Neon Demo" */ = { 803 | isa = XCConfigurationList; 804 | buildConfigurations = ( 805 | 27D2538F1BAF71AB00FA4FED /* Debug */, 806 | 27D253901BAF71AB00FA4FED /* Release */, 807 | ); 808 | defaultConfigurationIsVisible = 0; 809 | defaultConfigurationName = Release; 810 | }; 811 | /* End XCConfigurationList section */ 812 | }; 813 | rootObject = 27D2535E1BAF71AB00FA4FED /* Project object */; 814 | } 815 | -------------------------------------------------------------------------------- /Neon.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Neon.xcodeproj/xcshareddata/xcschemes/Neon Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Neon.xcodeproj/xcshareddata/xcschemes/Neon OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Neon.xcodeproj/xcshareddata/xcschemes/Neon iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Neon.xcodeproj/xcshareddata/xcschemes/NeonUnitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /NeonUnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](Screenshots/logo.png) 2 | 3 | ### Build dynamic and beautiful user interfaces like a boss, with Swift. 4 | 5 | [![License](https://img.shields.io/cocoapods/l/Neon.svg)](http://doge.mit-license.org) [![Build Status](https://img.shields.io/travis/mamaral/Neon.svg)](https://travis-ci.org/mamaral/Neon/) [![Badge w/ Version](https://img.shields.io/cocoapods/v/Neon.svg)](https://img.shields.io/cocoapods/v/Neon.svg) [![Coverage Status](https://coveralls.io/repos/mamaral/Neon/badge.svg?branch=master)](https://coveralls.io/r/mamaral/Neon?branch=master) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![CocoaPods](https://img.shields.io/cocoapods/dt/Neon.svg?maxAge=3600)](https://cocoapods.org/pods/Neon) 7 | 8 | Neon is built around how user interfaces are naturally and intuitively designed. No more springs and struts. No more whacky visual format language. No more auto layout constraints. We're not robots, so why should we build our UIs like we are? 9 | 10 | > ***Neon has been updated to Swift 3.0, but is still currently in beta!*** 11 | 12 | ## Install via CocoaPods 13 | 14 | You can use [Cocoapods](http://cocoapods.org/) to install `Neon` by adding it to your `Podfile`: 15 | 16 | ```ruby 17 | platform :ios, '9.0' 18 | use_frameworks! 19 | pod 'Neon' 20 | ``` 21 | 22 | ##Manual Installation 23 | 24 | 1. Download and drop `/Source` in your project. 25 | 2. Congratulations! 26 | 27 | 28 | To get the full benefits import `Neon` wherever you have a UIView operation: 29 | 30 | ``` swift 31 | import UIKit 32 | import Neon 33 | ``` 34 | 35 | ## Example 36 | 37 | ![facebook](Screenshots/side_by_side.png) 38 | 39 | Rather than design some arbitrary layout for a demonstration, I figured a good test for the practicality of Neon would be to replicate an existing screen from a major app, one that everyone could recognize. The above screenshot on the left is my profile in the Facebook app, and the screenshot on the right is from the Neon demo project. 40 | 41 | Facebook's profile screen was surely built using some form of `UITableView` or `UICollectionView`, but for the sake of simple demonstration I built the top-most major components of the profile in a normal `UIViewController`. After all the customization of the subviews to make them as close to Facebook's design as possible *(I tried my best)*, this is what I came up with for the layout: 42 | 43 | ```swift 44 | let isLandscape : Bool = UIDevice.currentDevice().orientation.isLandscape.boolValue 45 | let bannerHeight : CGFloat = view.height() * 0.43 46 | let avatarHeightMultipler : CGFloat = isLandscape ? 0.75 : 0.43 47 | let avatarSize = bannerHeight * avatarHeightMultipler 48 | 49 | searchBar.fillSuperview() 50 | bannerImageView.anchorAndFillEdge(.Top, xPad: 0, yPad: 0, otherSize: bannerHeight) 51 | bannerMaskView.fillSuperview() 52 | avatarImageView.anchorInCorner(.BottomLeft, xPad: 15, yPad: 15, width: avatarSize, height: avatarSize) 53 | nameLabel.alignAndFillWidth(align: .ToTheRightCentered, relativeTo: avatarImageView, padding: 15, height: 120) 54 | cameraButton.anchorInCorner(.BottomRight, xPad: 10, yPad: 7, width: 28, height: 28) 55 | buttonContainerView.alignAndFillWidth(align: .UnderCentered, relativeTo: bannerImageView, padding: 0, height: 62) 56 | buttonContainerView.groupAndFill(group: .Horizontal, views: [postButton, updateInfoButton, activityLogButton, moreButton], padding: 10) 57 | buttonContainerView2.alignAndFillWidth(align: .UnderCentered, relativeTo: buttonContainerView, padding: 0, height: 128) 58 | buttonContainerView2.groupAndFill(group: .Horizontal, views: [aboutView, photosView, friendsView], padding: 10) 59 | ``` 60 | 61 | ![portrait](Screenshots/portrait.png) 62 | 63 | Looks pretty good on every device size! Now, keep in mind you'll probably want constants defined for many of these size/padding values, in order to keep the code cleaner and easier to maintain, but I decided to use real numbers for most of the values to make the code less obscure when new people are reading through the demonstration. Now, ***unlike Facebook's iPhone app*** the layout built with Neon is ***dynamic***. It is able to handle rotation on all-sized devices with no problem: 64 | 65 | ![landscape](Screenshots/landscape.png) 66 | 67 | ###Not bad for 10 lines of code! 68 | 69 | 70 | Here's an intentionally convoluted example to show how easy it is to build very complex and dynamic layouts with Neon. The following layout was created with only *20 lines of code*. That's one line of code per view! While impressive, this layout is horrifying and should never be used in an actual app... ever... 71 | 72 | ![Demo](Screenshots/demo.gif) 73 | 74 | 75 | ## Anchoring Views 76 | 77 | ### Center 78 | 79 | There are a few ways you can anchor views using Neon, and the first and most simple is anchoring a view in the center of its superview: 80 | 81 | ```swift 82 | view1.anchorInCenter(width: size, height: size) 83 | ``` 84 | 85 | ![Center](Screenshots/center.png) 86 | 87 | 88 | ### Filling Superview 89 | 90 | Sometimes you want a view to fill its superview entirely, which couldn't be easier. 91 | 92 | ```swift 93 | view.fillSuperview() 94 | ``` 95 | 96 | Optionally, if you want a view to fill its superview with padding, you can provide padding instead: 97 | 98 | ```swift 99 | view1.fillSuperview(left: padding, right: padding, top: padding, bottom: padding) 100 | ``` 101 | 102 | ![Fill](Screenshots/fill.png) 103 | 104 | 105 | ### Corner 106 | 107 | The second anchoring method is anchoring a view in its superview's `Corner`. As you might have guessed, the four corners are `.TopLeft`, `.TopRight`, `.BottomLeft`, `.BottomRight`, and coupled with the `anchorInCorner()` function, you can easily anchor a view in any corner like this: 108 | 109 | ```swift 110 | view1.anchorInCorner(.TopLeft, xPad: padding, yPad: padding, width: size, height: size) 111 | view2.anchorInCorner(.TopRight, xPad: padding, yPad: padding, width: size, height: size) 112 | view3.anchorInCorner(.BottomLeft, xPad: padding, yPad: padding, width: size, height: size) 113 | view4.anchorInCorner(.BottomRight, xPad: padding, yPad: padding, width: size, height: size) 114 | ``` 115 | 116 | ![Corner](Screenshots/corner.png) 117 | 118 | 119 | ### Edge 120 | 121 | `Edge` is another pretty obvious one to follow - it specifies on what edge of its superview a view will be anchored to. The four types are `.Top`, `.Left`, `.Bottom`, or `.Right`, and similar to previous examples, you can use the `anchorToEdge()` function to anchor a view to an edge: 122 | 123 | ```swift 124 | view1.anchorToEdge(.Top, padding: padding, width: size, height: size) 125 | view2.anchorToEdge(.Left, padding: padding, width: size, height: size) 126 | view3.anchorToEdge(.Bottom, padding: padding, width: size, height: size) 127 | view4.anchorToEdge(.Right, padding: padding, width: size, height: size) 128 | ``` 129 | 130 | ![Edge](Screenshots/edge.png) 131 | 132 | 133 | 134 | ### Filling an edge 135 | 136 | Sometimes, you want to anchor a view against an edge, filling that edge; imagine something like a banner photo for a profile page. Again, this is made as simple as possible using the `anchorAndFillEdge()` function: 137 | 138 | ```swift 139 | view1.anchorAndFillEdge(.Top, xPad: padding, yPad: padding, otherSize: size) 140 | view2.anchorAndFillEdge(.Bottom, xPad: padding, yPad: padding, otherSize: size) 141 | view3.anchorAndFillEdge(.Left, xPad: padding, yPad: padding, otherSize: size) 142 | view4.anchorAndFillEdge(.Right, xPad: padding, yPad: padding, otherSize: size) 143 | ``` 144 | 145 | ![Fill Edge](Screenshots/fill_edge.png) 146 | 147 | 148 | > Note that `anchorAndFillEdge()` accepts a parameter called `otherSize`. That parameter is used to set the *other size* that isn't automatically calculated by filling the edge, meaning that if you specify that you want to anchor to and fill the top edge, the width will be automatically calculated, but the height is still unknown, so the value passed in to `otherSize` will be used to set the height. Subsequently, if you want to anchor to and fill the left edge, the height is automatically calculated and `otherSize` will be used to set the width of the view. 149 | > 150 | 151 | 152 | ## Align 153 | 154 | Now that we've anchored primary views, we can start making our UI more complex by aligning other views *relative to other sibling views*, using the (you guessed it) `Align` value. **Sibling views** are views that share the same superview directly. There are twelve `Align` types, and they are all pretty self-explanatory - here's an example using all twelve with the `align()` function: 155 | 156 | ``` 157 | view1.align(.AboveMatchingLeft, relativeTo: anchorView, padding: padding, width: size, height: size) 158 | view2.align(.AboveCentered, relativeTo: anchorView, padding: padding, width: size, height: size) 159 | view3.align(.AboveMatchingRight, relativeTo: anchorView, padding: padding, width: size, height: size) 160 | view4.align(.ToTheRightMatchingTop, relativeTo: anchorView, padding: padding, width: size, height: size) 161 | view5.align(.ToTheRightCentered, relativeTo: anchorView, padding: padding, width: size, height: size) 162 | view6.align(.ToTheRightMatchingBottom, relativeTo: anchorView, padding: padding, width: size, height: size) 163 | view7.align(.UnderMatchingRight, relativeTo: anchorView, padding: padding, width: size, height: size) 164 | view8.align(.UnderCentered, relativeTo: anchorView, padding: padding, width: size, height: size) 165 | view9.align(.UnderMatchingLeft, relativeTo: anchorView, padding: padding, width: size, height: size) 166 | view10.align(.ToTheLeftMatchingBottom, relativeTo: anchorView, padding: padding, width: size, height: size) 167 | view11.align(.ToTheLeftCentered, relativeTo: anchorView, padding: padding, width: size, height: size) 168 | view12.align(.ToTheLeftMatchingTop, relativeTo: anchorView, padding: padding, width: size, height: size) 169 | ``` 170 | 171 | ![Align](Screenshots/align.png) 172 | 173 | 174 | ## Align and fill 175 | 176 | You don't always know or want to specify the size of a view that you want to layout relative to another, but rather you want to either fill the width, height, or the entire rest of the superview, after aligning with the sibling. Combined with all the different alignment types discussed earlier, we're starting to see how more complex layouts can be built very easily: 177 | 178 | ```swift 179 | view2.alignAndFillWidth(align: .ToTheRightMatchingTop, relativeTo: view1, padding: padding, height: size / 2.0) 180 | view4.alignAndFillHeight(align: .AboveCentered, relativeTo: view3, padding: padding, width: size / 2.0) 181 | view6.alignAndFill(align: .ToTheLeftMatchingTop, relativeTo: view5, padding: padding) 182 | ``` 183 | 184 | ![Align Fill](Screenshots/align_fill.png) 185 | 186 | 187 | ## Align between 188 | 189 | Sometimes you want a view to sit between to other views, filling the space between them. Using alignBetweenHorizontal() and alignBetweenVertical(), doing that is super easy! Choose one of your sibling views you want to align your view relative to and pass that in as your `primaryView`. We will use the specified `align` parameter to match that `primaryView` appropriately, and automatically fill either the horizontal or vertical span between the it and the `secondaryView`. 190 | 191 | ```swift 192 | view1.alignBetweenHorizontal(align: .ToTheRightMatchingTop, primaryView: anchorViewA, secondaryView: anchorViewB, padding: padding, height: size) 193 | view2.alignBetweenVertical(align: .UnderCentered, primaryView: anchorViewB, secondaryView: anchorViewD, padding: padding, width: size) 194 | view3.alignBetweenHorizontal(align: .ToTheLeftMatchingBottom, primaryView: anchorViewD, secondaryView: anchorViewC, padding: padding, height: size) 195 | view4.alignBetweenVertical(align: .AboveMatchingRight, primaryView: anchorViewC, secondaryView: anchorViewA, padding: padding, width: size) 196 | ``` 197 | 198 | ![Align Between Fill](Screenshots/align_between_fill.png) 199 | 200 | ## What about labels? 201 | 202 | One of the more complicated parts of working with dynamic layouts, is dealing with labels that may have 1 -> n lines, and as such passing in a specific height isn't always possible without causing a migraine. Neon makes this easy by introducing the `AutoHeight` constant. Pass this value into methods that accept a `height` param, and we will first set the width of the frame, tell the view to `sizeToFit()` so the height is automatically set based on its contents, and then align the view appropriately. For example: 203 | 204 | ```swift 205 | testLabel.alignBetweenHorizontal(align: .ToTheRightMatchingBottom, primaryView: anchorViewA, secondaryView: anchorViewB, padding: padding, height: AutoHeight) 206 | ``` 207 | 208 | ![Auto Height 1](Screenshots/auto_height_1.png) 209 | 210 | Note that changing the text to something with more characters still produces the same desired result: 211 | 212 | ![Auto Height 2](Screenshots/auto_height_2.png) 213 | 214 | > It's important to note that the using `AutoHeight` with something like a `CALayer`, or passing it in to any of the grouping methods (see below) will have undesired consequences, as it almost doesn't *make sense* in this context. Use `AutoHeight` with anything that implements `sizeToFit()` and you should be OK. The vast majority of cases where you'll want to use this is with `UILabel` objects. 215 | 216 | ## What if I don't want to align them perfectly? 217 | 218 | Sometimes you don't want your views to align with their sibling views *exactly* - you may want to align them relative to their siblings, but with a slight offset. You can do this by adding the optional `offset` parameter to any of the above align methods to produce something like the following: 219 | 220 | ```swift 221 | view1.align(.ToTheRightMatchingTop, relativeTo: anchorViewA, padding: padding, width: size, height: size, offset: offset) 222 | view2.align(.UnderMatchingLeft, relativeTo: anchorViewA, padding: padding, width: size, height: size, offset: offset) 223 | ``` 224 | 225 | ![Offset](Screenshots/align_offset.png) 226 | 227 | ## Grouping 228 | 229 | Another common use-case is the *grouping* of sibling views, aligned in a row or column. Using what we've already learned about anchoring views in the center, in a corner, or against an edge, we can also do the same with groups of views! 230 | 231 | The primary difference with grouping, is that it is done by the *parent view,* or `superview` of a group of views. For example, let's let two different views center a group of their subviews in each of the two different `Group` configurations, `.Horizontal` and `.Vertical`: 232 | 233 | ```swift 234 | anchorViewA.groupInCenter(group: .Horizontal, views: [view1, view2, view3], padding: padding, width: size, height: size) 235 | anchorViewB.groupInCenter(group: .Vertical, views: [view4, view5, view6], padding: padding, width: size, height: size) 236 | ``` 237 | 238 | ![Group in center](Screenshots/group_in_center.png) 239 | 240 | How about grouping views in the corner? 241 | 242 | ```swift 243 | anchorViewA.groupInCorner(group: .Horizontal, views: [view1, view2, view3], inCorner: .TopLeft, padding: padding, width: size, height: size) 244 | anchorViewB.groupInCorner(group: .Vertical, views: [view4, view5, view6], inCorner: .BottomRight, padding: padding, width: size, height: size) 245 | ``` 246 | 247 | ![Group in corner](Screenshots/group_in_corner.png) 248 | 249 | As you might have expected, you can also group either horizontally and vertically against any edge as well: 250 | 251 | ```swift 252 | anchorViewA.groupAgainstEdge(group: .Horizontal, views: [view1, view2, view3], againstEdge: .Left, padding: padding, width: size, height: size) 253 | anchorViewB.groupAgainstEdge(group: .Vertical, views: [view4, view5, view6], againstEdge: .Bottom, padding: padding, width: size, height: size) 254 | ``` 255 | 256 | ![Group against edge](Screenshots/group_against_edge.png) 257 | 258 | Grouping views relative to a sibling view can be done as well: 259 | 260 | ```swift 261 | view.groupAndAlign(group: .Horizontal, andAlign: .ToTheRightMatchingTop, views: [view1, view2, view3], relativeTo: anchorViewA, padding: padding, width: size, height: size) 262 | view.groupAndAlign(group: .Vertical, andAlign: .UnderCentered, views: [view4, view5, view6], relativeTo: anchorViewA, padding: padding, width: size, height: size) 263 | ``` 264 | 265 | ![Group relative](Screenshots/group_relative.png) 266 | 267 | You can also specify that you want a group of subviews to fill their superview, either horizontally or vertically: 268 | 269 | ```swift 270 | anchorViewA.groupAndFill(group: .Horizontal, views: [view1, view2, view3], padding: padding) 271 | anchorViewB.groupAndFill(group: .Vertical, views: [view4, view5, view6], padding: padding) 272 | ``` 273 | ![Group and fill](Screenshots/group_and_fill.png) 274 | 275 | ## License 276 | 277 | The source is made available under the MIT license. See LICENSE.txt for details. 278 | -------------------------------------------------------------------------------- /Screenshots/align.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/align.png -------------------------------------------------------------------------------- /Screenshots/align_between_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/align_between_fill.png -------------------------------------------------------------------------------- /Screenshots/align_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/align_fill.png -------------------------------------------------------------------------------- /Screenshots/align_offset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/align_offset.png -------------------------------------------------------------------------------- /Screenshots/auto_height_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/auto_height_1.png -------------------------------------------------------------------------------- /Screenshots/auto_height_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/auto_height_2.png -------------------------------------------------------------------------------- /Screenshots/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/center.png -------------------------------------------------------------------------------- /Screenshots/corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/corner.png -------------------------------------------------------------------------------- /Screenshots/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/demo.gif -------------------------------------------------------------------------------- /Screenshots/edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/edge.png -------------------------------------------------------------------------------- /Screenshots/fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/fill.png -------------------------------------------------------------------------------- /Screenshots/fill_edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/fill_edge.png -------------------------------------------------------------------------------- /Screenshots/group_against_edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/group_against_edge.png -------------------------------------------------------------------------------- /Screenshots/group_and_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/group_and_fill.png -------------------------------------------------------------------------------- /Screenshots/group_in_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/group_in_center.png -------------------------------------------------------------------------------- /Screenshots/group_in_corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/group_in_corner.png -------------------------------------------------------------------------------- /Screenshots/group_relative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/group_relative.png -------------------------------------------------------------------------------- /Screenshots/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/landscape.png -------------------------------------------------------------------------------- /Screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/logo.png -------------------------------------------------------------------------------- /Screenshots/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/portrait.png -------------------------------------------------------------------------------- /Screenshots/side_by_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamaral/Neon/d11e6332bce5317b548b14c38cd3600643402512/Screenshots/side_by_side.png -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Mike Amaral. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/Neon.h: -------------------------------------------------------------------------------- 1 | // 2 | // Neon.h 3 | // Neon 4 | // 5 | // Created by Sean Cheng on 10/3/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | //! Project version number for Neon-iOS. 13 | FOUNDATION_EXPORT double NeonVersionNumber; 14 | 15 | //! Project version string for Neon-iOS. 16 | FOUNDATION_EXPORT const unsigned char NeonVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | 20 | 21 | -------------------------------------------------------------------------------- /Source/Neon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Neon.swift 3 | // Neon 4 | // 5 | // Created by Mike on 9/16/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | 16 | // MARK: AutoHeight 17 | // 18 | /// 19 | /// `CGFloat` constant used to specify that you want the height to be automatically calculated 20 | /// using `sizeToFit()`. 21 | /// 22 | public let AutoHeight : CGFloat = -1 23 | public let AutoWidth : CGFloat = -1 24 | 25 | 26 | // MARK: Corner 27 | // 28 | /// 29 | /// Specifies a corner of a frame. 30 | /// 31 | /// **topLeft**: The upper-left corner of the frame. 32 | /// 33 | /// **topRight**: The upper-right corner of the frame. 34 | /// 35 | /// **bottomLeft**: The bottom-left corner of the frame. 36 | /// 37 | /// **bottomRight**: The upper-right corner of the frame. 38 | /// 39 | public enum Corner { 40 | case topLeft 41 | case topRight 42 | case bottomLeft 43 | case bottomRight 44 | } 45 | 46 | 47 | // MARK: Edge 48 | // 49 | /// 50 | /// Specifies an edge, or face, of a frame. 51 | /// 52 | /// **top**: The top edge of the frame. 53 | /// 54 | /// **left**: The left edge of the frame. 55 | /// 56 | /// **bottom**: The bottom edge of the frame. 57 | /// 58 | /// **right**: The right edge of the frame. 59 | /// 60 | public enum Edge { 61 | case top 62 | case left 63 | case bottom 64 | case right 65 | } 66 | 67 | 68 | // MARK: Align Type 69 | // 70 | /// 71 | /// Specifies how a view will be aligned relative to the sibling view. 72 | /// 73 | /// **toTheRightMatchingTop**: Specifies that the view should be aligned to the right of a sibling, matching the 74 | /// top, or y origin, of the sibling's frame. 75 | /// 76 | /// **toTheRightMatchingBottom**: Specifies that the view should be aligned to the right of a sibling, matching 77 | /// the bottom, or max y value, of the sibling's frame. 78 | /// 79 | /// **toTheRightCentered**: Specifies that the view should be aligned to the right of a sibling, and will be centered 80 | /// to either match the vertical center of the sibling's frame or centered vertically within the superview, depending 81 | /// on the context. 82 | /// 83 | /// **toTheLeftMatchingTop**: Specifies that the view should be aligned to the left of a sibling, matching the top, 84 | /// or y origin, of the sibling's frame. 85 | /// 86 | /// **toTheLeftMatchingBottom**: Specifies that the view should be aligned to the left of a sibling, matching the 87 | /// bottom, or max y value, of the sibling's frame. 88 | /// 89 | /// **toTheLeftCentered**: Specifies that the view should be aligned to the left of a sibling, and will be centered 90 | /// to either match the vertical center of the sibling's frame or centered vertically within the superview, depending 91 | /// on the context. 92 | /// 93 | /// **underMatchingLeft**: Specifies that the view should be aligned under a sibling, matching the left, or x origin, 94 | /// of the sibling's frame. 95 | /// 96 | /// **underMatchingRight**: Specifies that the view should be aligned under a sibling, matching the right, or max y 97 | /// of the sibling's frame. 98 | /// 99 | /// **underCentered**: Specifies that the view should be aligned under a sibling, and will be centered to either match 100 | /// the horizontal center of the sibling's frame or centered horizontally within the superview, depending on the context. 101 | /// 102 | /// **aboveMatchingLeft**: Specifies that the view should be aligned above a sibling, matching the left, or x origin 103 | /// of the sibling's frame. 104 | /// 105 | /// **aboveMatchingRight**: Specifies that the view should be aligned above a sibling, matching the right, or max x 106 | /// of the sibling's frame. 107 | /// 108 | /// **aboveCentered**: Specifies that the view should be aligned above a sibling, and will be centered to either match 109 | /// the horizontal center of the sibling's frame or centered horizontally within the superview, depending on the context. 110 | /// 111 | public enum Align { 112 | case toTheRightMatchingTop 113 | case toTheRightMatchingBottom 114 | case toTheRightCentered 115 | case toTheLeftMatchingTop 116 | case toTheLeftMatchingBottom 117 | case toTheLeftCentered 118 | case underMatchingLeft 119 | case underMatchingRight 120 | case underCentered 121 | case aboveMatchingLeft 122 | case aboveMatchingRight 123 | case aboveCentered 124 | } 125 | 126 | 127 | // MARK: Group Type 128 | // 129 | /// 130 | /// Specifies how a group will be laid out. 131 | /// 132 | /// **horizontal**: Specifies that the views should be aligned relative to eachother horizontally. 133 | /// 134 | /// **vertical**: Specifies that the views should be aligned relative to eachother vertically. 135 | /// 136 | public enum Group { 137 | case horizontal 138 | case vertical 139 | } 140 | -------------------------------------------------------------------------------- /Source/NeonAlignable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeonAlignable.swift 3 | // Neon 4 | // 5 | // Created by Mike on 10/1/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | 16 | public protocol Alignable : Frameable {} 17 | 18 | public extension Alignable { 19 | 20 | /// Align a view relative to a sibling view in the same superview. 21 | /// 22 | /// - parameters: 23 | /// - align: The `Align` type used to specify where and how this view is aligned with its sibling. 24 | /// 25 | /// - relativeTo: The sibling view this view will be aligned relative to. **NOTE:** Ensure this sibling view shares 26 | /// the same superview as this view, and that the sibling view is not the same as this view, otherwise a 27 | /// `fatalError` is thrown. 28 | /// 29 | /// - padding: The padding to be applied between this view and the sibling view, which is applied differently 30 | /// depending on the `Align` specified. For example, if aligning `.ToTheRightOfMatchingTop` the padding is used 31 | /// to adjust the x origin of this view so it sits to the right of the sibling view, while the y origin is 32 | /// automatically calculated to match the sibling view. 33 | /// 34 | /// - width: The width of the view. 35 | /// 36 | /// - height: The height of the view. 37 | /// 38 | /// - offset: An optional parameter that will offset the view by the defined amount such that the view will not perfectly 39 | /// match the specified `Align`. For example, if you specify `.ToTheRightMatchingTop` and provide an offset value of `5`, the 40 | /// view's y origin will be lower than the sibling view's y origin by 5 points. 41 | /// 42 | public func align(_ align: Align, relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, height: CGFloat, offset: CGFloat = 0) { 43 | var xOrigin : CGFloat = 0.0 44 | var yOrigin : CGFloat = 0.0 45 | 46 | switch align { 47 | case .toTheRightMatchingTop: 48 | xOrigin = sibling.xMax + padding 49 | yOrigin = sibling.y + offset 50 | 51 | case .toTheRightMatchingBottom: 52 | xOrigin = sibling.xMax + padding 53 | yOrigin = sibling.yMax - height + offset 54 | 55 | case .toTheRightCentered: 56 | xOrigin = sibling.xMax + padding 57 | yOrigin = sibling.yMid - (height / 2.0) + offset 58 | 59 | case .toTheLeftMatchingTop: 60 | xOrigin = sibling.x - width - padding 61 | yOrigin = sibling.y + offset 62 | 63 | case .toTheLeftMatchingBottom: 64 | xOrigin = sibling.x - width - padding 65 | yOrigin = sibling.yMax - height + offset 66 | 67 | case .toTheLeftCentered: 68 | xOrigin = sibling.x - width - padding 69 | yOrigin = sibling.yMid - (height / 2.0) + offset 70 | 71 | case .underMatchingLeft: 72 | xOrigin = sibling.x + offset 73 | yOrigin = sibling.yMax + padding 74 | 75 | case .underMatchingRight: 76 | xOrigin = sibling.xMax - width + offset 77 | yOrigin = sibling.yMax + padding 78 | 79 | case .underCentered: 80 | xOrigin = sibling.xMid - (width / 2.0) + offset 81 | yOrigin = sibling.yMax + padding 82 | 83 | case .aboveMatchingLeft: 84 | xOrigin = sibling.x + offset 85 | yOrigin = sibling.y - padding - height 86 | 87 | case .aboveMatchingRight: 88 | xOrigin = sibling.xMax - width + offset 89 | yOrigin = sibling.y - padding - height 90 | 91 | case .aboveCentered: 92 | xOrigin = sibling.xMid - (width / 2.0) + offset 93 | yOrigin = sibling.y - padding - height 94 | } 95 | 96 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 97 | 98 | if height == AutoHeight { 99 | self.setDimensionAutomatically() 100 | self.align(align, relativeTo: sibling, padding: padding, width: width, height: self.height, offset: offset) 101 | } 102 | if width == AutoWidth { 103 | self.setDimensionAutomatically() 104 | self.align(align, relativeTo: sibling, padding: padding, width: self.width, height: height, offset: offset) 105 | } 106 | } 107 | 108 | 109 | 110 | /// Align a view relative to a sibling view in the same superview, and automatically expand the width to fill 111 | /// the superview with equal padding between the superview and sibling view. 112 | /// 113 | /// - parameters: 114 | /// - align: The `Align` type used to specify where and how this view is aligned with its sibling. 115 | /// 116 | /// - relativeTo: The sibling view this view will be aligned relative to. **NOTE:** Ensure this sibling view shares 117 | /// the same superview as this view, and that the sibling view is not the same as this view, otherwise a 118 | /// `fatalError` is thrown. 119 | /// 120 | /// - padding: The padding to be applied between this view, the sibling view and the superview. 121 | /// 122 | /// - height: The height of the view. 123 | /// 124 | /// - offset: An optional parameter that will offset the view by the defined amount such that the view will not perfectly 125 | /// match the specified `Align`. For example, if you specify `.ToTheRightMatchingTop` and provide an offset value of `5`, the 126 | /// view's y origin will be lower than the sibling view's y origin by 5 points. 127 | /// 128 | public func alignAndFillWidth(align: Align, relativeTo sibling: Frameable, padding: CGFloat, height: CGFloat, offset: CGFloat = 0) { 129 | let superviewWidth = superFrame.width 130 | var xOrigin : CGFloat = 0.0 131 | var yOrigin : CGFloat = 0.0 132 | var width : CGFloat = 0.0 133 | 134 | switch align { 135 | case .toTheRightMatchingTop: 136 | xOrigin = sibling.xMax + padding 137 | yOrigin = sibling.y + offset 138 | width = superviewWidth - xOrigin - padding 139 | 140 | case .toTheRightMatchingBottom: 141 | xOrigin = sibling.xMax + padding 142 | yOrigin = sibling.yMax - height + offset 143 | width = superviewWidth - xOrigin - padding 144 | 145 | case .toTheRightCentered: 146 | xOrigin = sibling.xMax + padding 147 | yOrigin = sibling.yMid - (height / 2.0) + offset 148 | width = superviewWidth - xOrigin - padding 149 | 150 | case .toTheLeftMatchingTop: 151 | xOrigin = padding 152 | yOrigin = sibling.y + offset 153 | width = sibling.x - (2 * padding) 154 | 155 | case .toTheLeftMatchingBottom: 156 | xOrigin = padding 157 | yOrigin = sibling.yMax - height + offset 158 | width = sibling.x - (2 * padding) 159 | 160 | case .toTheLeftCentered: 161 | xOrigin = padding 162 | yOrigin = sibling.yMid - (height / 2.0) + offset 163 | width = sibling.x - (2 * padding) 164 | 165 | case .underMatchingLeft: 166 | xOrigin = sibling.x + offset 167 | yOrigin = sibling.yMax + padding 168 | width = superviewWidth - xOrigin - padding 169 | 170 | case .underMatchingRight: 171 | xOrigin = padding + offset 172 | yOrigin = sibling.yMax + padding 173 | width = superviewWidth - (superviewWidth - sibling.xMax) - padding 174 | 175 | case .underCentered: 176 | xOrigin = padding + offset 177 | yOrigin = sibling.yMax + padding 178 | width = superviewWidth - (2 * padding) 179 | 180 | case .aboveMatchingLeft: 181 | xOrigin = sibling.x + offset 182 | yOrigin = sibling.y - padding - height 183 | width = superviewWidth - xOrigin - padding 184 | 185 | case .aboveMatchingRight: 186 | xOrigin = padding + offset 187 | yOrigin = sibling.y - padding - height 188 | width = superviewWidth - (superviewWidth - sibling.xMax) - padding 189 | 190 | case .aboveCentered: 191 | xOrigin = padding + offset 192 | yOrigin = sibling.y - padding - height 193 | width = superviewWidth - (2 * padding) 194 | } 195 | 196 | if width < 0.0 { 197 | width = 0.0 198 | } 199 | 200 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 201 | 202 | if height == AutoHeight { 203 | self.setDimensionAutomatically() 204 | self.alignAndFillWidth(align: align, relativeTo: sibling, padding: padding, height: self.height, offset: offset) 205 | } 206 | } 207 | 208 | 209 | /// Align a view relative to a sibling view in the same superview, and automatically expand the height to fill 210 | /// the superview with equal padding between the superview and sibling view. 211 | /// 212 | /// - parameters: 213 | /// - align: The `Align` type used to specify where and how this view is aligned with its sibling. 214 | /// 215 | /// - relativeTo: The sibling view this view will be aligned relative to. **NOTE:** Ensure this sibling view shares 216 | /// the same superview as this view, and that the sibling view is not the same as this view, otherwise a 217 | /// `fatalError` is thrown. 218 | /// 219 | /// - padding: The padding to be applied between this view, the sibling view and the superview. 220 | /// 221 | /// - width: The width of the view. 222 | /// 223 | /// - offset: An optional parameter that will offset the view by the defined amount such that the view will not perfectly 224 | /// match the specified `Align`. For example, if you specify `.ToTheRightMatchingTop` and provide an offset value of `5`, the 225 | /// view's y origin will be lower than the sibling view's y origin by 5 points. 226 | /// 227 | public func alignAndFillHeight(align: Align, relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, offset: CGFloat = 0) { 228 | let superviewHeight : CGFloat = superFrame.height 229 | var xOrigin : CGFloat = 0.0 230 | var yOrigin : CGFloat = 0.0 231 | var height : CGFloat = 0.0 232 | 233 | switch align { 234 | case .toTheRightMatchingTop: 235 | xOrigin = sibling.xMax + padding 236 | yOrigin = sibling.y + offset 237 | height = superviewHeight - sibling.y - padding 238 | 239 | case .toTheRightMatchingBottom: 240 | xOrigin = sibling.xMax + padding 241 | yOrigin = padding + offset 242 | height = superviewHeight - (superviewHeight - sibling.yMax) - padding 243 | 244 | case .toTheRightCentered: 245 | xOrigin = sibling.xMax + padding 246 | yOrigin = padding + offset 247 | height = superviewHeight - (2 * padding) 248 | 249 | case .toTheLeftMatchingTop: 250 | xOrigin = sibling.x - width - padding 251 | yOrigin = sibling.y + offset 252 | height = superviewHeight - sibling.y - padding 253 | 254 | case .toTheLeftMatchingBottom: 255 | xOrigin = sibling.x - width - padding 256 | yOrigin = padding + offset 257 | height = superviewHeight - (superviewHeight - sibling.yMax) - padding 258 | 259 | case .toTheLeftCentered: 260 | xOrigin = sibling.x - width - padding 261 | yOrigin = padding + offset 262 | height = superviewHeight - (2 * padding) 263 | 264 | case .underMatchingLeft: 265 | xOrigin = sibling.x + offset 266 | yOrigin = sibling.yMax + padding 267 | height = superviewHeight - yOrigin - padding 268 | 269 | case .underMatchingRight: 270 | xOrigin = sibling.xMax - width + offset 271 | yOrigin = sibling.yMax + padding 272 | height = superviewHeight - yOrigin - padding 273 | 274 | case .underCentered: 275 | xOrigin = sibling.xMid - (width / 2.0) + offset 276 | yOrigin = sibling.yMax + padding 277 | height = superviewHeight - yOrigin - padding 278 | 279 | case .aboveMatchingLeft: 280 | xOrigin = sibling.x + offset 281 | yOrigin = padding 282 | height = sibling.y - (2 * padding) 283 | 284 | case .aboveMatchingRight: 285 | xOrigin = sibling.xMax - width + offset 286 | yOrigin = padding 287 | height = sibling.y - (2 * padding) 288 | 289 | case .aboveCentered: 290 | xOrigin = sibling.xMid - (width / 2.0) + offset 291 | yOrigin = padding 292 | height = sibling.y - (2 * padding) 293 | } 294 | 295 | if height < 0.0 { 296 | height = 0.0 297 | } 298 | 299 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 300 | 301 | if height == AutoHeight { 302 | self.setDimensionAutomatically() 303 | self.alignAndFillHeight(align: align, relativeTo: sibling, padding: padding, width: self.height, offset: offset) 304 | } 305 | } 306 | 307 | 308 | /// Align a view relative to a sibling view in the same superview, and automatically expand the width AND height 309 | /// to fill the superview with equal padding between the superview and sibling view. 310 | /// 311 | /// - parameters: 312 | /// - align: The `Align` type used to specify where and how this view is aligned with its sibling. 313 | /// 314 | /// - relativeTo: The sibling view this view will be aligned relative to. **NOTE:** Ensure this sibling view shares 315 | /// the same superview as this view, and that the sibling view is not the same as this view, otherwise a 316 | /// `fatalError` is thrown. 317 | /// 318 | /// - padding: The padding to be applied between this view, the sibling view and the superview. 319 | /// 320 | /// - offset: An optional parameter that will offset the view by the defined amount such that the view will not perfectly 321 | /// match the specified `Align`. For example, if you specify `.ToTheRightMatchingTop` and provide an offset value of `5`, the 322 | /// view's y origin will be lower than the sibling view's y origin by 5 points. 323 | /// 324 | public func alignAndFill(align: Align, relativeTo sibling: Frameable, padding: CGFloat, offset: CGFloat = 0) { 325 | let superviewWidth : CGFloat = superFrame.width 326 | let superviewHeight : CGFloat = superFrame.height 327 | var xOrigin : CGFloat = 0.0 328 | var yOrigin : CGFloat = 0.0 329 | var width : CGFloat = 0.0 330 | var height : CGFloat = 0.0 331 | 332 | switch align { 333 | case .toTheRightMatchingTop: 334 | xOrigin = sibling.xMax + padding 335 | yOrigin = sibling.y + offset 336 | width = superviewWidth - xOrigin - padding 337 | height = superviewHeight - yOrigin - padding 338 | 339 | case .toTheRightMatchingBottom: 340 | xOrigin = sibling.xMax + padding 341 | yOrigin = padding + offset 342 | width = superviewWidth - xOrigin - padding 343 | height = superviewHeight - (superviewHeight - sibling.yMax) - padding 344 | 345 | case .toTheRightCentered: 346 | xOrigin = sibling.xMax + padding 347 | yOrigin = padding + offset 348 | width = superviewWidth - xOrigin - padding 349 | height = superviewHeight - (2 * padding) 350 | 351 | case .toTheLeftMatchingTop: 352 | xOrigin = padding 353 | yOrigin = sibling.y + offset 354 | width = superviewWidth - (superviewWidth - sibling.x) - (2 * padding) 355 | height = superviewHeight - yOrigin - padding 356 | 357 | case .toTheLeftMatchingBottom: 358 | xOrigin = padding 359 | yOrigin = padding + offset 360 | width = superviewWidth - (superviewWidth - sibling.x) - (2 * padding) 361 | height = superviewHeight - (superviewHeight - sibling.yMax) - padding 362 | 363 | case .toTheLeftCentered: 364 | xOrigin = padding 365 | yOrigin = padding + offset 366 | width = superviewWidth - (superviewWidth - sibling.x) - (2 * padding) 367 | height = superviewHeight - (2 * padding) 368 | 369 | case .underMatchingLeft: 370 | xOrigin = sibling.x + offset 371 | yOrigin = sibling.yMax + padding 372 | width = superviewWidth - xOrigin - padding 373 | height = superviewHeight - yOrigin - padding 374 | 375 | case .underMatchingRight: 376 | xOrigin = padding + offset 377 | yOrigin = sibling.yMax + padding 378 | width = superviewWidth - (superviewWidth - sibling.xMax) - padding 379 | height = superviewHeight - yOrigin - padding 380 | 381 | case .underCentered: 382 | xOrigin = padding + offset 383 | yOrigin = sibling.yMax + padding 384 | width = superviewWidth - (2 * padding) 385 | height = superviewHeight - yOrigin - padding 386 | 387 | case .aboveMatchingLeft: 388 | xOrigin = sibling.x + offset 389 | yOrigin = padding 390 | width = superviewWidth - xOrigin - padding 391 | height = superviewHeight - (superviewHeight - sibling.y) - (2 * padding) 392 | 393 | case .aboveMatchingRight: 394 | xOrigin = padding + offset 395 | yOrigin = padding 396 | width = superviewWidth - (superviewWidth - sibling.xMax) - padding 397 | height = superviewHeight - (superviewHeight - sibling.y) - (2 * padding) 398 | 399 | case .aboveCentered: 400 | xOrigin = padding + offset 401 | yOrigin = padding 402 | width = superviewWidth - (2 * padding) 403 | height = superviewHeight - (superviewHeight - sibling.y) - (2 * padding) 404 | } 405 | 406 | if width < 0.0 { 407 | width = 0.0 408 | } 409 | 410 | if height < 0.0 { 411 | height = 0.0 412 | } 413 | 414 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 415 | } 416 | 417 | 418 | /// Align a view between two sibling views horizontally, automatically expanding the width to extend the full 419 | /// horizontal span between the `primaryView` and the `secondaryView`, with equal padding on both sides. 420 | /// 421 | /// - parameters: 422 | /// - align: The `Align` type used to specify where and how this view is aligned with the primary view. 423 | /// 424 | /// - primaryView: The primary sibling view this view will be aligned relative to. 425 | /// 426 | /// - secondaryView: The secondary sibling view this view will be automatically sized to fill the space between. 427 | /// 428 | /// - padding: The horizontal padding to be applied between this view and both sibling views. 429 | /// 430 | /// - height: The height of the view. 431 | /// 432 | /// - offset: An optional parameter that will offset the view by the defined amount such that the view will not perfectly 433 | /// match the specified `Align`. For example, if you specify `.ToTheRightMatchingTop` and provide an offset value of `5`, the 434 | /// view's y origin will be lower than the sibling view's y origin by 5 points. 435 | /// 436 | public func alignBetweenHorizontal(align: Align, primaryView: Frameable, secondaryView: Frameable, padding: CGFloat, height: CGFloat, offset: CGFloat = 0) { 437 | let superviewWidth : CGFloat = superFrame.width 438 | var xOrigin : CGFloat = 0.0 439 | var yOrigin : CGFloat = 0.0 440 | var width : CGFloat = 0.0 441 | 442 | switch align { 443 | case .toTheRightMatchingTop: 444 | xOrigin = primaryView.xMax + padding 445 | yOrigin = primaryView.y + offset 446 | width = superviewWidth - primaryView.xMax - (superviewWidth - secondaryView.x) - (2 * padding) 447 | 448 | case .toTheRightMatchingBottom: 449 | xOrigin = primaryView.xMax + padding 450 | yOrigin = primaryView.yMax - height + offset 451 | width = superviewWidth - primaryView.xMax - (superviewWidth - secondaryView.x) - (2 * padding) 452 | 453 | case .toTheRightCentered: 454 | xOrigin = primaryView.xMax + padding 455 | yOrigin = primaryView.yMid - (height / 2.0) + offset 456 | width = superviewWidth - primaryView.xMax - (superviewWidth - secondaryView.x) - (2 * padding) 457 | 458 | case .toTheLeftMatchingTop: 459 | xOrigin = secondaryView.xMax + padding 460 | yOrigin = primaryView.y + offset 461 | width = superviewWidth - secondaryView.xMax - (superviewWidth - primaryView.x) - (2 * padding) 462 | 463 | case .toTheLeftMatchingBottom: 464 | xOrigin = secondaryView.xMax + padding 465 | yOrigin = primaryView.yMax - height + offset 466 | width = superviewWidth - secondaryView.xMax - (superviewWidth - primaryView.x) - (2 * padding) 467 | 468 | case .toTheLeftCentered: 469 | xOrigin = secondaryView.xMax + padding 470 | yOrigin = primaryView.yMid - (height / 2.0) + offset 471 | width = superviewWidth - secondaryView.xMax - (superviewWidth - primaryView.x) - (2 * padding) 472 | 473 | case .underMatchingLeft, .underMatchingRight, .underCentered, .aboveMatchingLeft, .aboveMatchingRight, .aboveCentered: 474 | fatalError("[NEON] Invalid Align specified for alignBetweenHorizonal().") 475 | } 476 | 477 | if width < 0.0 { 478 | width = 0.0 479 | } 480 | 481 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 482 | 483 | if height == AutoHeight { 484 | self.setDimensionAutomatically() 485 | self.alignBetweenHorizontal(align: align, primaryView: primaryView, secondaryView: secondaryView, padding: padding, height: self.height) 486 | } 487 | } 488 | 489 | 490 | /// Align a view between two sibling views vertically, automatically expanding the height to extend the full 491 | /// vertical span between the `primaryView` and the `secondaryView`, with equal padding above and below. 492 | /// 493 | /// - parameters: 494 | /// - align: The `Align` type used to specify where and how this view is aligned with the primary view. 495 | /// 496 | /// - primaryView: The primary sibling view this view will be aligned relative to. 497 | /// 498 | /// - secondaryView: The secondary sibling view this view will be automatically sized to fill the space between. 499 | /// 500 | /// - padding: The horizontal padding to be applied between this view and both sibling views. 501 | /// 502 | /// - width: The width of the view. 503 | /// 504 | /// - offset: An optional parameter that will offset the view by the defined amount such that the view will not perfectly 505 | /// match the specified `Align`. For example, if you specify `.ToTheRightMatchingTop` and provide an offset value of `5`, the 506 | /// view's y origin will be lower than the sibling view's y origin by 5 points. 507 | /// 508 | public func alignBetweenVertical(align: Align, primaryView: Frameable, secondaryView: Frameable, padding: CGFloat, width: CGFloat, offset: CGFloat = 0) { 509 | let superviewHeight : CGFloat = superFrame.height 510 | var xOrigin : CGFloat = 0.0 511 | var yOrigin : CGFloat = 0.0 512 | var height : CGFloat = 0.0 513 | 514 | switch align { 515 | case .underMatchingLeft: 516 | xOrigin = primaryView.x + offset 517 | yOrigin = primaryView.yMax + padding 518 | height = superviewHeight - primaryView.yMax - (superviewHeight - secondaryView.y) - (2 * padding) 519 | 520 | case .underMatchingRight: 521 | xOrigin = primaryView.xMax - width + offset 522 | yOrigin = primaryView.yMax + padding 523 | height = superviewHeight - primaryView.yMax - (superviewHeight - secondaryView.y) - (2 * padding) 524 | 525 | case .underCentered: 526 | xOrigin = primaryView.xMid - (width / 2.0) + offset 527 | yOrigin = primaryView.yMax + padding 528 | height = superviewHeight - primaryView.yMax - (superviewHeight - secondaryView.y) - (2 * padding) 529 | 530 | case .aboveMatchingLeft: 531 | xOrigin = primaryView.x + offset 532 | yOrigin = secondaryView.yMax + padding 533 | height = superviewHeight - secondaryView.yMax - (superviewHeight - primaryView.y) - (2 * padding) 534 | 535 | case .aboveMatchingRight: 536 | xOrigin = primaryView.xMax - width + offset 537 | yOrigin = secondaryView.yMax + padding 538 | height = superviewHeight - secondaryView.yMax - (superviewHeight - primaryView.y) - (2 * padding) 539 | 540 | case .aboveCentered: 541 | xOrigin = primaryView.xMid - (width / 2.0) + offset 542 | yOrigin = secondaryView.yMax + padding 543 | height = superviewHeight - secondaryView.yMax - (superviewHeight - primaryView.y) - (2 * padding) 544 | 545 | case .toTheLeftMatchingTop, .toTheLeftMatchingBottom, .toTheLeftCentered, .toTheRightMatchingTop, .toTheRightMatchingBottom, .toTheRightCentered: 546 | fatalError("[NEON] Invalid Align specified for alignBetweenVertical().") 547 | } 548 | 549 | if height < 0 { 550 | height = 0 551 | } 552 | 553 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 554 | 555 | if width == AutoWidth { 556 | self.setDimensionAutomatically() 557 | self.alignBetweenVertical(align: align, primaryView: primaryView, secondaryView: secondaryView, padding: padding, width: self.height, offset: offset) 558 | } 559 | 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /Source/NeonAnchorable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeonAnchorable.swift 3 | // Neon 4 | // 5 | // Created by Mike on 10/1/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | 16 | public protocol Anchorable : Frameable {} 17 | 18 | public extension Anchorable { 19 | 20 | /// Fill the superview, with optional padding values. 21 | /// 22 | /// - note: If you don't want padding, simply call `fillSuperview()` with no parameters. 23 | /// 24 | /// - parameters: 25 | /// - left: The padding between the left side of the view and the superview. 26 | /// 27 | /// - right: The padding between the right side of the view and the superview. 28 | /// 29 | /// - top: The padding between the top of the view and the superview. 30 | /// 31 | /// - bottom: The padding between the bottom of the view and the superview. 32 | /// 33 | public func fillSuperview(left: CGFloat = 0, right: CGFloat = 0, top: CGFloat = 0, bottom: CGFloat = 0) { 34 | let width : CGFloat = superFrame.width - (left + right) 35 | let height : CGFloat = superFrame.height - (top + bottom) 36 | 37 | frame = CGRect(x: left, y: top, width: width, height: height) 38 | } 39 | 40 | 41 | /// Anchor a view in the center of its superview. 42 | /// 43 | /// - parameters: 44 | /// - width: The width of the view. 45 | /// 46 | /// - height: The height of the view. 47 | /// 48 | public func anchorInCenter(width: CGFloat, height: CGFloat) { 49 | let xOrigin : CGFloat = (superFrame.width / 2.0) - (width / 2.0) 50 | let yOrigin : CGFloat = (superFrame.height / 2.0) - (height / 2.0) 51 | 52 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 53 | 54 | if height == AutoHeight { 55 | self.setDimensionAutomatically() 56 | self.anchorInCenter(width: width, height: self.height) 57 | } 58 | 59 | if width == AutoWidth { 60 | self.setDimensionAutomatically() 61 | self.anchorInCenter(width: self.width, height: height) 62 | } 63 | } 64 | 65 | 66 | /// Anchor a view in one of the four corners of its superview. 67 | /// 68 | /// - parameters: 69 | /// - corner: The `CornerType` value used to specify in which corner the view will be anchored. 70 | /// 71 | /// - xPad: The *horizontal* padding applied to the view inside its superview, which can be applied 72 | /// to the left or right side of the view, depending upon the `CornerType` specified. 73 | /// 74 | /// - yPad: The *vertical* padding applied to the view inside its supeview, which will either be on 75 | /// the top or bottom of the view, depending upon the `CornerType` specified. 76 | /// 77 | /// - width: The width of the view. 78 | /// 79 | /// - height: The height of the view. 80 | /// 81 | public func anchorInCorner(_ corner: Corner, xPad: CGFloat, yPad: CGFloat, width: CGFloat, height: CGFloat) { 82 | var xOrigin : CGFloat = 0.0 83 | var yOrigin : CGFloat = 0.0 84 | 85 | switch corner { 86 | case .topLeft: 87 | xOrigin = xPad 88 | yOrigin = yPad 89 | 90 | case .bottomLeft: 91 | xOrigin = xPad 92 | yOrigin = superFrame.height - height - yPad 93 | 94 | case .topRight: 95 | xOrigin = superFrame.width - width - xPad 96 | yOrigin = yPad 97 | 98 | case .bottomRight: 99 | xOrigin = superFrame.width - width - xPad 100 | yOrigin = superFrame.height - height - yPad 101 | } 102 | 103 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 104 | 105 | if height == AutoHeight { 106 | self.setDimensionAutomatically() 107 | self.anchorInCorner(corner, xPad: xPad, yPad: yPad, width: width, height: self.height) 108 | } 109 | 110 | if width == AutoWidth { 111 | self.setDimensionAutomatically() 112 | self.anchorInCorner(corner, xPad: xPad, yPad: yPad, width: self.width, height: height) 113 | } 114 | 115 | } 116 | 117 | 118 | /// Anchor a view in its superview, centered on a given edge. 119 | /// 120 | /// - parameters: 121 | /// - edge: The `Edge` used to specify which face of the superview the view 122 | /// will be anchored against and centered relative to. 123 | /// 124 | /// - padding: The padding applied to the view inside its superview. How this padding is applied 125 | /// will vary depending on the `Edge` provided. Views centered against the top or bottom of 126 | /// their superview will have the padding applied above or below them respectively, whereas views 127 | /// centered against the left or right side of their superview will have the padding applied to the 128 | /// right and left sides respectively. 129 | /// 130 | /// - width: The width of the view. 131 | /// 132 | /// - height: The height of the view. 133 | /// 134 | public func anchorToEdge(_ edge: Edge, padding: CGFloat, width: CGFloat, height: CGFloat) { 135 | var xOrigin : CGFloat = 0.0 136 | var yOrigin : CGFloat = 0.0 137 | 138 | switch edge { 139 | case .top: 140 | xOrigin = (superFrame.width / 2.0) - (width / 2.0) 141 | yOrigin = padding 142 | 143 | case .left: 144 | xOrigin = padding 145 | yOrigin = (superFrame.height / 2.0) - (height / 2.0) 146 | 147 | case .bottom: 148 | xOrigin = (superFrame.width / 2.0) - (width / 2.0) 149 | yOrigin = superFrame.height - height - padding 150 | 151 | case .right: 152 | xOrigin = superFrame.width - width - padding 153 | yOrigin = (superFrame.height / 2.0) - (height / 2.0) 154 | } 155 | 156 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 157 | 158 | if height == AutoHeight { 159 | self.setDimensionAutomatically() 160 | self.anchorToEdge(edge, padding: padding, width: width, height: self.height) 161 | } 162 | if width == AutoWidth { 163 | self.setDimensionAutomatically() 164 | self.anchorToEdge(edge, padding: padding, width: self.width, height: height) 165 | } 166 | } 167 | 168 | 169 | /// Anchor a view in its superview, centered on a given edge and filling either the width or 170 | /// height of that edge. For example, views anchored to the `.Top` or `.Bottom` will have 171 | /// their widths automatically sized to fill their superview, with the xPad applied to both 172 | /// the left and right sides of the view. 173 | /// 174 | /// - parameters: 175 | /// - edge: The `Edge` used to specify which face of the superview the view 176 | /// will be anchored against, centered relative to, and expanded to fill. 177 | /// 178 | /// - xPad: The horizontal padding applied to the view inside its superview. If the `Edge` 179 | /// specified is `.Top` or `.Bottom`, this padding will be applied to the left and right sides 180 | /// of the view when it fills the width superview. 181 | /// 182 | /// - yPad: The vertical padding applied to the view inside its superview. If the `Edge` 183 | /// specified is `.Left` or `.Right`, this padding will be applied to the top and bottom sides 184 | /// of the view when it fills the height of the superview. 185 | /// 186 | /// - otherSize: The size parameter that is *not automatically calculated* based on the provided 187 | /// edge. For example, views anchored to the `.Top` or `.Bottom` will have their widths automatically 188 | /// calculated, so `otherSize` will be applied to their height, and subsequently views anchored to 189 | /// the `.Left` and `.Right` will have `otherSize` applied to their width as their heights are 190 | /// automatically calculated. 191 | /// 192 | public func anchorAndFillEdge(_ edge: Edge, xPad: CGFloat, yPad: CGFloat, otherSize: CGFloat) { 193 | var xOrigin : CGFloat = 0.0 194 | var yOrigin : CGFloat = 0.0 195 | var width : CGFloat = 0.0 196 | var height : CGFloat = 0.0 197 | var autoSize : Bool = false 198 | 199 | switch edge { 200 | case .top: 201 | xOrigin = xPad 202 | yOrigin = yPad 203 | width = superFrame.width - (2 * xPad) 204 | height = otherSize 205 | autoSize = true 206 | 207 | case .left: 208 | xOrigin = xPad 209 | yOrigin = yPad 210 | width = otherSize 211 | height = superFrame.height - (2 * yPad) 212 | 213 | case .bottom: 214 | xOrigin = xPad 215 | yOrigin = superFrame.height - otherSize - yPad 216 | width = superFrame.width - (2 * xPad) 217 | height = otherSize 218 | autoSize = true 219 | 220 | case .right: 221 | xOrigin = superFrame.width - otherSize - xPad 222 | yOrigin = yPad 223 | width = otherSize 224 | height = superFrame.height - (2 * yPad) 225 | } 226 | 227 | frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 228 | 229 | if height == AutoHeight && autoSize { 230 | self.setDimensionAutomatically() 231 | self.anchorAndFillEdge(edge, xPad: xPad, yPad: yPad, otherSize: self.height) 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /Source/NeonExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeonExtensions.swift 3 | // Neon 4 | // 5 | // Created by Mike on 03/08/2016. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | typealias View = UIView 12 | #else 13 | import Cocoa 14 | typealias View = NSView 15 | #endif 16 | 17 | // MARK: UIView implementation of the Neon protocols. 18 | // 19 | extension View : Frameable, Anchorable, Alignable, Groupable { 20 | public var superFrame: CGRect { 21 | guard let superview = superview else { 22 | return CGRect.zero 23 | } 24 | 25 | return superview.frame 26 | } 27 | 28 | public func setDimensionAutomatically() { 29 | #if os(iOS) 30 | self.sizeToFit() 31 | #else 32 | self.autoresizesSubviews = true 33 | self.autoresizingMask = [.width, .height] 34 | #endif 35 | } 36 | } 37 | 38 | 39 | // MARK: CALayer implementation of the Neon protocols. 40 | // 41 | extension CALayer : Frameable, Anchorable, Alignable, Groupable { 42 | public var superFrame: CGRect { 43 | guard let superlayer = superlayer else { 44 | return CGRect.zero 45 | } 46 | 47 | return superlayer.frame 48 | } 49 | 50 | public func setDimensionAutomatically() { /* no-op here as this shouldn't apply to CALayers */ } 51 | } 52 | -------------------------------------------------------------------------------- /Source/NeonFrameable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeonFrameable.swift 3 | // Neon 4 | // 5 | // Created by Mike on 10/1/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | 16 | /// Types adopting the `Frameable` protocol calculate specific `frame` information, as well as provide the 17 | /// frame information about their `superview` or `superlayer`. 18 | /// 19 | public protocol Frameable : class { 20 | 21 | var frame: CGRect { get set } 22 | var superFrame: CGRect { get } 23 | 24 | /// Get the x origin of a view. 25 | /// 26 | /// - returns: The minimum x value of the view's frame. 27 | /// 28 | /// Example 29 | /// ------- 30 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 31 | /// frame.x() // returns 10.0 32 | /// 33 | var x: CGFloat { get } 34 | 35 | 36 | /// Get the mid x of a view. 37 | /// 38 | /// - returns: The middle x value of the view's frame 39 | /// 40 | /// Example 41 | /// ------- 42 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 43 | /// frame.midX() // returns 7.5 44 | /// 45 | var xMid: CGFloat { get } 46 | 47 | 48 | /// Get the max x of a view. 49 | /// 50 | /// - returns: The maximum x value of the view's frame 51 | /// 52 | /// Example 53 | /// ------- 54 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 55 | /// frame.maxX() // returns 15.0 56 | /// 57 | var xMax: CGFloat { get } 58 | 59 | 60 | /// Get the y origin of a view. 61 | /// 62 | /// - returns: The minimum y value of the view's frame. 63 | /// 64 | /// Example 65 | /// ------- 66 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 67 | /// frame.y() // returns 20.0 68 | /// 69 | var y: CGFloat { get } 70 | 71 | 72 | /// Get the mid y of a view. 73 | /// 74 | /// - returns: The middle y value of the view's frame 75 | /// 76 | /// Example 77 | /// ------- 78 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 79 | /// frame.midY() // returns 13.5 80 | /// 81 | var yMid: CGFloat { get } 82 | 83 | 84 | /// Get the max y of a view. 85 | /// 86 | /// - returns: The maximum y value of the view's frame. 87 | /// 88 | /// Example 89 | /// ------- 90 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 91 | /// frame.maxY() // returns 27.0 92 | /// 93 | var yMax: CGFloat { get } 94 | 95 | 96 | /// Get the width of a view. 97 | /// 98 | /// - returns: The width of the view's frame. 99 | /// 100 | /// Example 101 | /// ------- 102 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 103 | /// frame.width() // returns 5.0 104 | /// 105 | var width: CGFloat { get } 106 | 107 | 108 | /// Get the height of a view. 109 | /// 110 | /// - returns: The height of the view's frame. 111 | /// 112 | /// Example 113 | /// ------- 114 | /// let frame = CGRectMake(10.0, 20.0, 5.0, 7.0) 115 | /// frame.height() // returns 7.0 116 | /// 117 | var height: CGFloat { get } 118 | 119 | 120 | /// *To be used internally* TODO: Determine how to make this either private or internal. 121 | /// 122 | /// 123 | func setDimensionAutomatically() 124 | } 125 | 126 | 127 | extension Frameable { 128 | 129 | public var x: CGFloat { 130 | return frame.minX 131 | } 132 | 133 | public var xMid: CGFloat { 134 | return frame.minX + (frame.width / 2.0) 135 | } 136 | 137 | public var xMax: CGFloat { 138 | return frame.maxX 139 | } 140 | 141 | public var y: CGFloat { 142 | return frame.minY 143 | } 144 | 145 | public var yMid: CGFloat { 146 | return frame.minY + (frame.height / 2.0) 147 | } 148 | 149 | public var yMax: CGFloat { 150 | return frame.maxY 151 | } 152 | 153 | public var width: CGFloat { 154 | return frame.width 155 | } 156 | 157 | public var height: CGFloat { 158 | return frame.height 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Source/NeonGroupable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeonGroupable.swift 3 | // Neon 4 | // 5 | // Created by Mike on 10/1/15. 6 | // Copyright © 2015 Mike Amaral. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | 16 | public protocol Groupable : Frameable {} 17 | 18 | public extension Groupable { 19 | 20 | /// Tell a view to group an array of its subviews centered, specifying the padding between each subview, 21 | /// as well as the size of each. 22 | /// 23 | /// - parameters: 24 | /// - group: The `Group` type specifying if the subviews will be laid out horizontally or vertically in the center. 25 | /// 26 | /// - views: The array of views to grouped in the center. Depending on if the views are gouped horizontally 27 | /// or vertically, they will be positioned in order from left-to-right and top-to-bottom, respectively. 28 | /// 29 | /// - padding: The padding to be applied between the subviews. 30 | /// 31 | /// - width: The width of each subview. 32 | /// 33 | /// - height: The height of each subview. 34 | /// 35 | public func groupInCenter(group: Group, views: [Frameable], padding: CGFloat, width: CGFloat, height: CGFloat) { 36 | if views.count == 0 { 37 | print("[NEON] Warning: No subviews provided to groupInCenter().") 38 | return 39 | } 40 | 41 | var xOrigin : CGFloat = 0.0 42 | var yOrigin : CGFloat = 0.0 43 | var xAdjust : CGFloat = 0.0 44 | var yAdjust : CGFloat = 0.0 45 | 46 | switch group { 47 | case .horizontal: 48 | xOrigin = (self.width - (CGFloat(views.count) * width) - (CGFloat(views.count - 1) * padding)) / 2.0 49 | yOrigin = (self.height / 2.0) - (height / 2.0) 50 | xAdjust = width + padding 51 | 52 | case .vertical: 53 | xOrigin = (self.width / 2.0) - (width / 2.0) 54 | yOrigin = (self.height - (CGFloat(views.count) * height) - (CGFloat(views.count - 1) * padding)) / 2.0 55 | yAdjust = height + padding 56 | } 57 | 58 | for view in views { 59 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 60 | 61 | xOrigin += xAdjust 62 | yOrigin += yAdjust 63 | } 64 | } 65 | 66 | 67 | /// Tell a view to group an array of its subviews in one of its corners, specifying the padding between each subview, 68 | /// as well as the size of each. 69 | /// 70 | /// - parameters: 71 | /// - group: The `Group` type specifying if the subviews will be laid out horizontally or vertically in the corner. 72 | /// 73 | /// - views: The array of views to grouped in the specified corner. Depending on if the views are gouped horizontally 74 | /// or vertically, they will be positioned in order from left-to-right and top-to-bottom, respectively. 75 | /// 76 | /// - inCorner: The specified corner the views will be grouped in. 77 | /// 78 | /// - padding: The padding to be applied between the subviews and their superview. 79 | /// 80 | /// - width: The width of each subview. 81 | /// 82 | /// - height: The height of each subview. 83 | /// 84 | public func groupInCorner(group: Group, views: [Frameable], inCorner corner: Corner, padding: CGFloat, width: CGFloat, height: CGFloat) { 85 | switch group { 86 | case .horizontal: 87 | groupInCornerHorizontal(views: views, inCorner: corner, padding: padding, width: width, height: height) 88 | 89 | case .vertical: 90 | groupInCornerVertical(views: views, inCorner: corner, padding: padding, width: width, height: height) 91 | } 92 | } 93 | 94 | 95 | /// Tell a view to group an array of its subviews against one of its edges, specifying the padding between each subview 96 | /// and their superview, as well as the size of each. 97 | /// 98 | /// - parameters: 99 | /// - group: The `Group` type specifying if the subviews will be laid out horizontally or vertically against the specified 100 | /// edge. 101 | /// 102 | /// - views: The array of views to grouped against the spcified edge. Depending on if the views are gouped horizontally 103 | /// or vertically, they will be positioned in-order from left-to-right and top-to-bottom, respectively. 104 | /// 105 | /// - againstEdge: The specified edge the views will be grouped against. 106 | /// 107 | /// - padding: The padding to be applied between each of the subviews and their superview. 108 | /// 109 | /// - width: The width of each subview. 110 | /// 111 | /// - height: The height of each subview. 112 | /// 113 | public func groupAgainstEdge(group: Group, views: [Frameable], againstEdge edge: Edge, padding: CGFloat, width: CGFloat, height: CGFloat) { 114 | if views.count == 0 { 115 | print("[NEON] Warning: No subviews provided to groupAgainstEdge().") 116 | return 117 | } 118 | 119 | var xOrigin : CGFloat = 0.0 120 | var yOrigin : CGFloat = 0.0 121 | var xAdjust : CGFloat = 0.0 122 | var yAdjust : CGFloat = 0.0 123 | 124 | switch edge { 125 | case .top: 126 | if group == .horizontal { 127 | xOrigin = (self.width - (CGFloat(views.count) * width) - (CGFloat(views.count - 1) * padding)) / 2.0 128 | xAdjust = width + padding 129 | } else { 130 | xOrigin = (self.width / 2.0) - (width / 2.0) 131 | yAdjust = height + padding 132 | } 133 | 134 | yOrigin = padding 135 | 136 | case .left: 137 | if group == .horizontal { 138 | yOrigin = (self.height / 2.0) - (height / 2.0) 139 | xAdjust = width + padding 140 | } else { 141 | yOrigin = (self.height - (CGFloat(views.count) * height) - (CGFloat(views.count - 1) * padding)) / 2.0 142 | yAdjust = height + padding 143 | } 144 | 145 | xOrigin = padding 146 | 147 | case .bottom: 148 | if group == .horizontal { 149 | xOrigin = (self.width - (CGFloat(views.count) * width) - (CGFloat(views.count - 1) * padding)) / 2.0 150 | yOrigin = self.height - height - padding 151 | xAdjust = width + padding 152 | } else { 153 | xOrigin = (self.width / 2.0) - (width / 2.0) 154 | yOrigin = self.height - (CGFloat(views.count) * height) - (CGFloat(views.count) * padding) 155 | yAdjust = height + padding 156 | } 157 | 158 | case .right: 159 | if group == .horizontal { 160 | xOrigin = self.width - (CGFloat(views.count) * width) - (CGFloat(views.count) * padding) 161 | yOrigin = (self.height / 2.0) - (height / 2.0) 162 | xAdjust = width + padding 163 | } else { 164 | xOrigin = self.width - width - padding 165 | yOrigin = (self.height - (CGFloat(views.count) * height) - (CGFloat(views.count - 1) * padding)) / 2.0 166 | yAdjust = height + padding 167 | } 168 | } 169 | 170 | for view in views { 171 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 172 | 173 | xOrigin += xAdjust 174 | yOrigin += yAdjust 175 | } 176 | } 177 | 178 | 179 | /// Tell a view to group an array of its subviews relative to another of that view's subview, specifying the padding between 180 | /// each. 181 | /// 182 | /// - parameters: 183 | /// - group: The `Group` type specifying if the subviews will be laid out horizontally or vertically against the specified 184 | /// sibling. 185 | /// 186 | /// - andAlign: the `Align` type specifying how the views will be aligned relative to the sibling. 187 | /// 188 | /// - views: The array of views to grouped against the sibling. Depending on if the views are gouped horizontally 189 | /// or vertically, they will be positioned in-order from left-to-right and top-to-bottom, respectively. 190 | /// 191 | /// - relativeTo: The sibling view that the views will be aligned relative to. 192 | /// 193 | /// - padding: The padding to be applied between each of the subviews and the sibling. 194 | /// 195 | /// - width: The width of each subview. 196 | /// 197 | /// - height: The height of each subview. 198 | /// 199 | public func groupAndAlign(group: Group, andAlign align: Align, views: [Frameable], relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, height: CGFloat) { 200 | switch group { 201 | case .horizontal: 202 | groupAndAlignHorizontal(align: align, views: views, relativeTo: sibling, padding: padding, width: width, height: height) 203 | 204 | case .vertical: 205 | groupAndAlignVertical(align: align, views: views, relativeTo: sibling, padding: padding, width: width, height: height) 206 | } 207 | } 208 | 209 | 210 | /// Tell a view to group an array of its subviews filling the width and height of the superview, specifying the padding between 211 | /// each subview and the superview. 212 | /// 213 | /// - parameters: 214 | /// - group: The `Group` type specifying if the subviews will be laid out horizontally or vertically. 215 | /// 216 | /// - views: The array of views to be grouped against the sibling. Depending on if the views are grouped horizontally 217 | /// or vertically, they will be positions in-order from left-to-right and top-to-bottom, respectively. 218 | /// 219 | /// - padding: The padding to be applied between each of the subviews and the sibling. 220 | /// 221 | public func groupAndFill(group: Group, views: [Frameable], padding: CGFloat) { 222 | if views.count == 0 { 223 | print("[NEON] Warning: No subviews provided to groupAndFill().") 224 | return 225 | } 226 | 227 | var xOrigin : CGFloat = padding 228 | var yOrigin : CGFloat = padding 229 | var width : CGFloat = 0.0 230 | var height : CGFloat = 0.0 231 | var xAdjust : CGFloat = 0.0 232 | var yAdjust : CGFloat = 0.0 233 | 234 | switch group { 235 | case .horizontal: 236 | width = (self.width - (CGFloat(views.count + 1) * padding)) / CGFloat(views.count) 237 | height = self.height - (2 * padding) 238 | xAdjust = width + padding 239 | 240 | case .vertical: 241 | width = self.width - (2 * padding) 242 | height = (self.height - (CGFloat(views.count + 1) * padding)) / CGFloat(views.count) 243 | yAdjust = height + padding 244 | } 245 | 246 | for view in views { 247 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 248 | 249 | xOrigin += xAdjust 250 | yOrigin += yAdjust 251 | } 252 | } 253 | 254 | 255 | 256 | // MARK: Private utils 257 | // 258 | fileprivate func groupInCornerHorizontal(views: [Frameable], inCorner corner: Corner, padding: CGFloat, width: CGFloat, height: CGFloat) { 259 | if views.count == 0 { 260 | print("[NEON] Warning: No subviews provided to groupInCorner().") 261 | return 262 | } 263 | 264 | var xOrigin : CGFloat = 0.0 265 | var yOrigin : CGFloat = 0.0 266 | let xAdjust : CGFloat = width + padding 267 | 268 | switch corner { 269 | case .topLeft: 270 | xOrigin = padding 271 | yOrigin = padding 272 | 273 | case .topRight: 274 | xOrigin = self.width - ((CGFloat(views.count) * width) + (CGFloat(views.count) * padding)) 275 | yOrigin = padding 276 | 277 | case .bottomLeft: 278 | xOrigin = padding 279 | yOrigin = self.height - height - padding 280 | 281 | case .bottomRight: 282 | xOrigin = self.width - ((CGFloat(views.count) * width) + (CGFloat(views.count) * padding)) 283 | yOrigin = self.height - height - padding 284 | } 285 | 286 | for view in views { 287 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 288 | 289 | xOrigin += xAdjust 290 | } 291 | } 292 | 293 | fileprivate func groupInCornerVertical(views: [Frameable], inCorner corner: Corner, padding: CGFloat, width: CGFloat, height: CGFloat) { 294 | if views.count == 0 { 295 | print("[NEON] Warning: No subviews provided to groupInCorner().") 296 | return 297 | } 298 | 299 | var xOrigin : CGFloat = 0.0 300 | var yOrigin : CGFloat = 0.0 301 | let yAdjust : CGFloat = height + padding 302 | 303 | switch corner { 304 | case .topLeft: 305 | xOrigin = padding 306 | yOrigin = padding 307 | 308 | case .topRight: 309 | xOrigin = self.width - width - padding 310 | yOrigin = padding 311 | 312 | case .bottomLeft: 313 | xOrigin = padding 314 | yOrigin = self.height - ((CGFloat(views.count) * height) + (CGFloat(views.count) * padding)) 315 | 316 | case .bottomRight: 317 | xOrigin = self.width - width - padding 318 | yOrigin = self.height - ((CGFloat(views.count) * height) + (CGFloat(views.count) * padding)) 319 | } 320 | 321 | for view in views { 322 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 323 | 324 | yOrigin += yAdjust 325 | } 326 | } 327 | 328 | fileprivate func groupAndAlignHorizontal(align: Align, views: [Frameable], relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, height: CGFloat) { 329 | if views.count == 0 { 330 | print("[NEON] Warning: No subviews provided to groupAndAlign().") 331 | return 332 | } 333 | 334 | var xOrigin : CGFloat = 0.0 335 | var yOrigin : CGFloat = 0.0 336 | let xAdjust : CGFloat = width + padding 337 | 338 | switch align { 339 | case .toTheRightMatchingTop: 340 | xOrigin = sibling.xMax + padding 341 | yOrigin = sibling.y 342 | 343 | case .toTheRightMatchingBottom: 344 | xOrigin = sibling.xMax + padding 345 | yOrigin = sibling.yMax - height 346 | 347 | case .toTheRightCentered: 348 | xOrigin = sibling.xMax + padding 349 | yOrigin = sibling.yMid - (height / 2.0) 350 | 351 | case .toTheLeftMatchingTop: 352 | xOrigin = sibling.x - (CGFloat(views.count) * width) - (CGFloat(views.count) * padding) 353 | yOrigin = sibling.y 354 | 355 | case .toTheLeftMatchingBottom: 356 | xOrigin = sibling.x - (CGFloat(views.count) * width) - (CGFloat(views.count) * padding) 357 | yOrigin = sibling.yMax - height 358 | 359 | case .toTheLeftCentered: 360 | xOrigin = sibling.x - (CGFloat(views.count) * width) - (CGFloat(views.count) * padding) 361 | yOrigin = sibling.yMid - (height / 2.0) 362 | 363 | case .underMatchingLeft: 364 | xOrigin = sibling.x 365 | yOrigin = sibling.yMax + padding 366 | 367 | case .underMatchingRight: 368 | xOrigin = sibling.xMax - (CGFloat(views.count) * width) - (CGFloat(views.count - 1) * padding) 369 | yOrigin = sibling.yMax + padding 370 | 371 | case .underCentered: 372 | xOrigin = sibling.xMid - ((CGFloat(views.count) * width) + (CGFloat(views.count - 1) * padding)) / 2.0 373 | yOrigin = sibling.yMax + padding 374 | 375 | case .aboveMatchingLeft: 376 | xOrigin = sibling.x 377 | yOrigin = sibling.y - height - padding 378 | 379 | case .aboveMatchingRight: 380 | xOrigin = sibling.xMax - (CGFloat(views.count) * width) - (CGFloat(views.count - 1) * padding) 381 | yOrigin = sibling.y - height - padding 382 | 383 | case .aboveCentered: 384 | xOrigin = sibling.xMid - ((CGFloat(views.count) * width) + (CGFloat(views.count - 1) * padding)) / 2.0 385 | yOrigin = sibling.y - height - padding 386 | } 387 | 388 | for view in views { 389 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 390 | 391 | xOrigin += xAdjust 392 | } 393 | } 394 | 395 | fileprivate func groupAndAlignVertical(align: Align, views: [Frameable], relativeTo sibling: Frameable, padding: CGFloat, width: CGFloat, height: CGFloat) { 396 | if views.count == 0 { 397 | print("[NEON] Warning: No subviews provided to groupAndAlign().") 398 | return 399 | } 400 | 401 | var xOrigin : CGFloat = 0.0 402 | var yOrigin : CGFloat = 0.0 403 | let yAdjust : CGFloat = height + padding 404 | 405 | switch align { 406 | case .toTheRightMatchingTop: 407 | xOrigin = sibling.xMax + padding 408 | yOrigin = sibling.y 409 | 410 | case .toTheRightMatchingBottom: 411 | xOrigin = sibling.xMax + padding 412 | yOrigin = sibling.yMax - (CGFloat(views.count) * height) - (CGFloat(views.count - 1) * padding) 413 | 414 | case .toTheRightCentered: 415 | xOrigin = sibling.xMax + padding 416 | yOrigin = sibling.yMid - ((CGFloat(views.count) * height) + CGFloat(views.count - 1) * padding) / 2.0 417 | 418 | case .toTheLeftMatchingTop: 419 | xOrigin = sibling.x - width - padding 420 | yOrigin = sibling.y 421 | 422 | case .toTheLeftMatchingBottom: 423 | xOrigin = sibling.x - width - padding 424 | yOrigin = sibling.yMax - (CGFloat(views.count) * height) - (CGFloat(views.count - 1) * padding) 425 | 426 | case .toTheLeftCentered: 427 | xOrigin = sibling.x - width - padding 428 | yOrigin = sibling.yMid - ((CGFloat(views.count) * height) + CGFloat(views.count - 1) * padding) / 2.0 429 | 430 | case .underMatchingLeft: 431 | xOrigin = sibling.x 432 | yOrigin = sibling.yMax + padding 433 | 434 | case .underMatchingRight: 435 | xOrigin = sibling.xMax - width 436 | yOrigin = sibling.yMax + padding 437 | 438 | case .underCentered: 439 | xOrigin = sibling.xMid - (width / 2.0) 440 | yOrigin = sibling.yMax + padding 441 | 442 | case .aboveMatchingLeft: 443 | xOrigin = sibling.x 444 | yOrigin = sibling.y - (CGFloat(views.count) * height) - (CGFloat(views.count) * padding) 445 | 446 | case .aboveMatchingRight: 447 | xOrigin = sibling.xMax - width 448 | yOrigin = sibling.y - (CGFloat(views.count) * height) - (CGFloat(views.count) * padding) 449 | 450 | case .aboveCentered: 451 | xOrigin = sibling.xMid - (width / 2.0) 452 | yOrigin = sibling.y - (CGFloat(views.count) * height) - (CGFloat(views.count) * padding) 453 | } 454 | 455 | for view in views { 456 | view.frame = CGRect(x: xOrigin, y: yOrigin, width: width, height: height) 457 | 458 | yOrigin += yAdjust 459 | } 460 | } 461 | } 462 | --------------------------------------------------------------------------------