diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..8c50098 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..06b0ff8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 daniele margutti + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..804f497 --- /dev/null +++ b/Package.swift @@ -0,0 +1,5 @@ +import PackageDescription + +let package = Package( + name: "ScrollingStackContainer" +) diff --git a/README.md b/README.md index bac9c61..938463b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,124 @@ -# ScrollingStackContainer -Scrolling stack-view container +

+ScrollingStackContainer +

+ +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CI Status](https://travis-ci.org/malcommac/ScrollingStackContainer.svg)](https://travis-ci.org/malcommac/ScrollingStackContainer) [![Version](https://img.shields.io/cocoapods/v/ScrollingStackContainer.svg?style=flat)](http://cocoadocs.org/docsets/ScrollingStackContainer) [![License](https://img.shields.io/cocoapods/l/ScrollingStackContainer.svg?style=flat)](http://cocoadocs.org/docsets/ScrollingStackContainer) [![Platform](https://img.shields.io/cocoapods/p/ScrollingStackContainer.svg?style=flat)](http://cocoadocs.org/docsets/ScrollingStackContainer) + +

★★ Star our github repository to help us! ★★

+

Created by Daniele Margutti (@danielemargutti)

+ +`ScrollingStackContainer` is an efficient scrolling `UIStackView` replacement, more suitable in situations when you are building a scrolling container with an heterogeneous number of items. +It allows you to stack vertically `UIViewController` instances where the view is a simple fixed-height `UIView` or a `UICollectionView` or `UITableView`. + +## Motivation +A full article about the motivation behind this class is available on Medium (or in my personal blog). Check it here for full details about how the class works. + +UITableView and UICollectionView are great when you need to display a number of relatively simple cells; when your layout became complex you may need to create different UIViewController which manages each different kind layout. +These view controller may contains simple fixed-height `UIView` or complex `UICollectionView`/`UITableView`; in these cases you need to be careful because expading your scrollviews will destroy internal iOS caching mechanism. +`ScrollingStackContainer` allows you to stack view controllers easily without any worry; it manages view controllers with scrolling collections automatically in order to reduce the amount of memory usage. + +## How to use it + +It's very simple to implement: for **fixed height views** you should set a valid `height` (grater than zero). This can be done in your `UIViewController`'s subclass in one of the following ways: + +* set an height constraint on `UIViewController`’s .view +* … or return a value into `preferredContentSize()` function +* … or set the height via `self.frame` +* … or by implementing the `StackContainable` protocol and returning `.view(height: )` with a valid value. + +For view controllers which **contains inner’s scroll views** (for example table or collections) you need to implement the `StackContainble` protocol by returning `.scroll(, )`. + +After that you should simply set the `viewControllers` property of your `ScrollingStackContainer` subclass: + +```swift + // view controller's view are stacked vertically in order + self.viewControllers = [controller_2,controller_1,controller_3] +``` + +That’s all! All the business logic is managed by the class and you can enjoy! + +See the example in this repository to get a live preview! + +The following example is a vertical stack which contains a fixed height view controller, a view controller with a table inside and another fixed height controller. + +![ScrollingStackContainer](https://raw.githubusercontent.com/malcommac/ScrollingStackContainer/develop/example.gif) + +## You also may like + +Do you like `ScrollingStackContainer`? I'm also working on several other opensource libraries. + +Take a look here: + +* **[Hydra](https://github.com/malcommac/Hydra)** - Promises & Await/Async in Swift - Write better async code in Swift +* **[SwiftLocation](https://github.com/malcommac/SwiftLocation)** - CoreLocation and Beacon Monitoring on steroid! +* **[SwiftRichString](https://github.com/malcommac/SwiftRichString)** - Elegant and painless attributed string in Swift +* **[SwiftScanner](https://github.com/malcommac/SwiftScanner)** - String scanner in pure Swift with full unicode support +* **[SwiftSimplify](https://github.com/malcommac/SwiftSimplify)** - Tiny high-performance Swift Polyline Simplification Library +* **[SwiftMsgPack](https://github.com/malcommac/SwiftMsgPack)** - MsgPack Encoder/Decoder in Swit + +## Installation + +`ScrollingStackContainer` supports multiple methods for installing the library in a project. + +## Installation with CocoaPods + +[CocoaPods](http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like `ScrollingStackContainer` in your projects. You can install it with the following command: + +```bash +$ gem install cocoapods +``` + +#### Podfile + +To integrate ScrollingStackContainer into your Xcode project using CocoaPods, specify it in your `Podfile`: + +```ruby +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' + +target 'TargetName' do +use_frameworks! +pod 'ScrollingStackContainer', '~> 0.5' +end +``` + +Then, run the following command: + +```bash +$ pod install +``` + +### Installation with Carthage + +[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. + +You can install Carthage with [Homebrew](http://brew.sh/) using the following command: + +```bash +$ brew update +$ brew install carthage +``` + +To integrate ScrollingStackContainer into your Xcode project using Carthage, specify it in your `Cartfile`: + +```ogdl +github "malcommac/ScrollingStackContainer" ~> 0.5 +``` + +Run `carthage` to build the framework and drag the built `ScrollingStackContainer.framework` into your Xcode project. + +## Requirements + +Current version is compatible with: + +* Swift 3.1 +* iOS 8 or later +* ...and virtually any platform which is compatible with Swift 3 and implements the Swift Foundation Library + + +## Credits & License +ScrollingStackContainer is owned and maintained by [Daniele Margutti](http://www.danielemargutti.com/) along with main contributions of [Jeroen Houtzager](https://github.com/Hout). + +As open source creation any help is welcome! + +The code of this library is licensed under MIT License; you can use it in commercial products without any limitation. diff --git a/ScrollingStackContainer-logo-source.sketch b/ScrollingStackContainer-logo-source.sketch new file mode 100644 index 0000000..9669422 Binary files /dev/null and b/ScrollingStackContainer-logo-source.sketch differ diff --git a/ScrollingStackContainer.podspec b/ScrollingStackContainer.podspec new file mode 100644 index 0000000..06925a2 --- /dev/null +++ b/ScrollingStackContainer.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |spec| + spec.name = 'ScrollingStackContainer' + spec.version = '0.5.0' + spec.summary = 'Efficient Scrolling Stack Container' + spec.homepage = 'https://github.com/malcommac/ScrollingStackContainer' + spec.license = { :type => 'MIT', :file => 'LICENSE' } + spec.author = { 'Daniele Margutti' => 'me@danielemargutti.com' } + spec.social_media_url = 'http://twitter.com/danielemargutti' + spec.source = { :git => 'https://github.com/malcommac/ScrollingStackContainer.git', :tag => "#{spec.version}" } + spec.source_files = 'Sources/**/*.swift' + spec.ios.deployment_target = '8.0' + spec.requires_arc = true + spec.module_name = 'ScrollingStackContainer' +end diff --git a/ScrollingStackContainer/.DS_Store b/ScrollingStackContainer/.DS_Store new file mode 100644 index 0000000..4517294 Binary files /dev/null and b/ScrollingStackContainer/.DS_Store differ diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5831fe9 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj @@ -0,0 +1,515 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 64CB314A1EEA011900D88EAF /* ScrollingStackContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 64CB31481EEA011900D88EAF /* ScrollingStackContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 64CB314D1EEA011900D88EAF /* ScrollingStackContainer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64CB31461EEA011900D88EAF /* ScrollingStackContainer.framework */; }; + 64CB314E1EEA011900D88EAF /* ScrollingStackContainer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 64CB31461EEA011900D88EAF /* ScrollingStackContainer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 64CB31531EEA012900D88EAF /* ScrollingStackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84FA01EE9F81600EC8295 /* ScrollingStackController.swift */; }; + 64E84F861EE9F7D600EC8295 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84F851EE9F7D600EC8295 /* AppDelegate.swift */; }; + 64E84F881EE9F7D600EC8295 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84F871EE9F7D600EC8295 /* ViewController.swift */; }; + 64E84F8B1EE9F7D600EC8295 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64E84F891EE9F7D600EC8295 /* Main.storyboard */; }; + 64E84F8D1EE9F7D600EC8295 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64E84F8C1EE9F7D600EC8295 /* Assets.xcassets */; }; + 64E84F901EE9F7D600EC8295 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64E84F8E1EE9F7D600EC8295 /* LaunchScreen.storyboard */; }; + 64E84F9B1EE9F7EB00EC8295 /* ContainerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84F971EE9F7EB00EC8295 /* ContainerList.swift */; }; + 64E84F9C1EE9F7EB00EC8295 /* Containers.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64E84F981EE9F7EB00EC8295 /* Containers.storyboard */; }; + 64E84F9D1EE9F7EB00EC8295 /* ContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84F991EE9F7EB00EC8295 /* ContainerView.swift */; }; + 64E84F9E1EE9F7EB00EC8295 /* ContainerView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84F9A1EE9F7EB00EC8295 /* ContainerView2.swift */; }; + 64E84FA11EE9F81600EC8295 /* ScrollingStackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E84FA01EE9F81600EC8295 /* ScrollingStackController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 64CB314B1EEA011900D88EAF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 64E84F7A1EE9F7D600EC8295 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 64CB31451EEA011900D88EAF; + remoteInfo = ScrollingStackContainer; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 64CB31521EEA011900D88EAF /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 64CB314E1EEA011900D88EAF /* ScrollingStackContainer.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 64CB31461EEA011900D88EAF /* ScrollingStackContainer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ScrollingStackContainer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 64CB31481EEA011900D88EAF /* ScrollingStackContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScrollingStackContainer.h; sourceTree = ""; }; + 64CB31491EEA011900D88EAF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 64E84F821EE9F7D600EC8295 /* ScrollingStackContainerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ScrollingStackContainerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 64E84F851EE9F7D600EC8295 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 64E84F871EE9F7D600EC8295 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 64E84F8A1EE9F7D600EC8295 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 64E84F8C1EE9F7D600EC8295 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 64E84F8F1EE9F7D600EC8295 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 64E84F911EE9F7D600EC8295 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 64E84F971EE9F7EB00EC8295 /* ContainerList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerList.swift; sourceTree = ""; }; + 64E84F981EE9F7EB00EC8295 /* Containers.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Containers.storyboard; sourceTree = ""; }; + 64E84F991EE9F7EB00EC8295 /* ContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerView.swift; sourceTree = ""; }; + 64E84F9A1EE9F7EB00EC8295 /* ContainerView2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerView2.swift; sourceTree = ""; }; + 64E84FA01EE9F81600EC8295 /* ScrollingStackController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScrollingStackController.swift; path = ../Sources/ScrollingStackContainer/ScrollingStackController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 64CB31421EEA011900D88EAF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 64E84F7F1EE9F7D600EC8295 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 64CB314D1EEA011900D88EAF /* ScrollingStackContainer.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 64CB31471EEA011900D88EAF /* ScrollingStackContainer */ = { + isa = PBXGroup; + children = ( + 64CB31481EEA011900D88EAF /* ScrollingStackContainer.h */, + 64CB31491EEA011900D88EAF /* Info.plist */, + ); + path = ScrollingStackContainer; + sourceTree = ""; + }; + 64CB31541EEA015200D88EAF /* Library */ = { + isa = PBXGroup; + children = ( + 64E84FA01EE9F81600EC8295 /* ScrollingStackController.swift */, + ); + name = Library; + sourceTree = ""; + }; + 64E84F791EE9F7D600EC8295 = { + isa = PBXGroup; + children = ( + 64CB31541EEA015200D88EAF /* Library */, + 64E84F841EE9F7D600EC8295 /* Example */, + 64CB31471EEA011900D88EAF /* ScrollingStackContainer */, + 64E84F831EE9F7D600EC8295 /* Products */, + ); + sourceTree = ""; + }; + 64E84F831EE9F7D600EC8295 /* Products */ = { + isa = PBXGroup; + children = ( + 64E84F821EE9F7D600EC8295 /* ScrollingStackContainerExample.app */, + 64CB31461EEA011900D88EAF /* ScrollingStackContainer.framework */, + ); + name = Products; + sourceTree = ""; + }; + 64E84F841EE9F7D600EC8295 /* Example */ = { + isa = PBXGroup; + children = ( + 64E84F851EE9F7D600EC8295 /* AppDelegate.swift */, + 64E84F871EE9F7D600EC8295 /* ViewController.swift */, + 64E84F9F1EE9F80300EC8295 /* Containers */, + 64E84F891EE9F7D600EC8295 /* Main.storyboard */, + 64E84F8C1EE9F7D600EC8295 /* Assets.xcassets */, + 64E84F8E1EE9F7D600EC8295 /* LaunchScreen.storyboard */, + 64E84F911EE9F7D600EC8295 /* Info.plist */, + ); + name = Example; + path = ScrollingStackContainer; + sourceTree = ""; + }; + 64E84F9F1EE9F80300EC8295 /* Containers */ = { + isa = PBXGroup; + children = ( + 64E84F971EE9F7EB00EC8295 /* ContainerList.swift */, + 64E84F981EE9F7EB00EC8295 /* Containers.storyboard */, + 64E84F991EE9F7EB00EC8295 /* ContainerView.swift */, + 64E84F9A1EE9F7EB00EC8295 /* ContainerView2.swift */, + ); + name = Containers; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 64CB31431EEA011900D88EAF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 64CB314A1EEA011900D88EAF /* ScrollingStackContainer.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 64CB31451EEA011900D88EAF /* ScrollingStackContainer */ = { + isa = PBXNativeTarget; + buildConfigurationList = 64CB314F1EEA011900D88EAF /* Build configuration list for PBXNativeTarget "ScrollingStackContainer" */; + buildPhases = ( + 64CB31411EEA011900D88EAF /* Sources */, + 64CB31421EEA011900D88EAF /* Frameworks */, + 64CB31431EEA011900D88EAF /* Headers */, + 64CB31441EEA011900D88EAF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ScrollingStackContainer; + productName = ScrollingStackContainer; + productReference = 64CB31461EEA011900D88EAF /* ScrollingStackContainer.framework */; + productType = "com.apple.product-type.framework"; + }; + 64E84F811EE9F7D600EC8295 /* ScrollingStackContainerExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 64E84F941EE9F7D600EC8295 /* Build configuration list for PBXNativeTarget "ScrollingStackContainerExample" */; + buildPhases = ( + 64E84F7E1EE9F7D600EC8295 /* Sources */, + 64E84F7F1EE9F7D600EC8295 /* Frameworks */, + 64E84F801EE9F7D600EC8295 /* Resources */, + 64CB31521EEA011900D88EAF /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 64CB314C1EEA011900D88EAF /* PBXTargetDependency */, + ); + name = ScrollingStackContainerExample; + productName = ScrollingStackContainer; + productReference = 64E84F821EE9F7D600EC8295 /* ScrollingStackContainerExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 64E84F7A1EE9F7D600EC8295 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = danielemargutti; + TargetAttributes = { + 64CB31451EEA011900D88EAF = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = E5DU3FA699; + ProvisioningStyle = Automatic; + }; + 64E84F811EE9F7D600EC8295 = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = E5DU3FA699; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 64E84F7D1EE9F7D600EC8295 /* Build configuration list for PBXProject "ScrollingStackContainer" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 64E84F791EE9F7D600EC8295; + productRefGroup = 64E84F831EE9F7D600EC8295 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 64E84F811EE9F7D600EC8295 /* ScrollingStackContainerExample */, + 64CB31451EEA011900D88EAF /* ScrollingStackContainer */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 64CB31441EEA011900D88EAF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 64E84F801EE9F7D600EC8295 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 64E84F901EE9F7D600EC8295 /* LaunchScreen.storyboard in Resources */, + 64E84F8D1EE9F7D600EC8295 /* Assets.xcassets in Resources */, + 64E84F9C1EE9F7EB00EC8295 /* Containers.storyboard in Resources */, + 64E84F8B1EE9F7D600EC8295 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 64CB31411EEA011900D88EAF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 64CB31531EEA012900D88EAF /* ScrollingStackController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 64E84F7E1EE9F7D600EC8295 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 64E84F9D1EE9F7EB00EC8295 /* ContainerView.swift in Sources */, + 64E84F881EE9F7D600EC8295 /* ViewController.swift in Sources */, + 64E84F861EE9F7D600EC8295 /* AppDelegate.swift in Sources */, + 64E84F9B1EE9F7EB00EC8295 /* ContainerList.swift in Sources */, + 64E84FA11EE9F81600EC8295 /* ScrollingStackController.swift in Sources */, + 64E84F9E1EE9F7EB00EC8295 /* ContainerView2.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 64CB314C1EEA011900D88EAF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 64CB31451EEA011900D88EAF /* ScrollingStackContainer */; + targetProxy = 64CB314B1EEA011900D88EAF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 64E84F891EE9F7D600EC8295 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 64E84F8A1EE9F7D600EC8295 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 64E84F8E1EE9F7D600EC8295 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 64E84F8F1EE9F7D600EC8295 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 64CB31501EEA011900D88EAF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = E5DU3FA699; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ScrollingStackContainer/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 64CB31511EEA011900D88EAF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = E5DU3FA699; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ScrollingStackContainer/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 64E84F921EE9F7D600EC8295 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 64E84F931EE9F7D600EC8295 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 64E84F951EE9F7D600EC8295 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = E5DU3FA699; + INFOPLIST_FILE = ScrollingStackContainer/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 64E84F961EE9F7D600EC8295 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = E5DU3FA699; + INFOPLIST_FILE = ScrollingStackContainer/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 64CB314F1EEA011900D88EAF /* Build configuration list for PBXNativeTarget "ScrollingStackContainer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 64CB31501EEA011900D88EAF /* Debug */, + 64CB31511EEA011900D88EAF /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + 64E84F7D1EE9F7D600EC8295 /* Build configuration list for PBXProject "ScrollingStackContainer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 64E84F921EE9F7D600EC8295 /* Debug */, + 64E84F931EE9F7D600EC8295 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 64E84F941EE9F7D600EC8295 /* Build configuration list for PBXNativeTarget "ScrollingStackContainerExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 64E84F951EE9F7D600EC8295 /* Debug */, + 64E84F961EE9F7D600EC8295 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 64E84F7A1EE9F7D600EC8295 /* Project object */; +} diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..d808716 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..3189cfc Binary files /dev/null and b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme new file mode 100644 index 0000000..af69e54 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/ScrollingStackContainer Example.xcscheme b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/ScrollingStackContainer Example.xcscheme new file mode 100644 index 0000000..162acfd --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/ScrollingStackContainer Example.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/xcschememanagement.plist b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..3627df9 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + ScrollingStackContainer Example.xcscheme + + orderHint + 0 + + ScrollingStackContainer.xcscheme_^#shared#^_ + + orderHint + 1 + + + SuppressBuildableAutocreation + + 64CB31451EEA011900D88EAF + + primary + + + 64E84F811EE9F7D600EC8295 + + primary + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer/AppDelegate.swift b/ScrollingStackContainer/ScrollingStackContainer/AppDelegate.swift new file mode 100644 index 0000000..97ece20 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/AppDelegate.swift @@ -0,0 +1,67 @@ +// ScrollingStackController +// Efficient Scrolling Container for UIViewControllers +// +// Created by Daniele Margutti. +// Copyright © 2017 Daniele Margutti. All rights reserved. +// +// Web: http://www.danielemargutti.com +// Email: hello@danielemargutti.com +// Twitter: @danielemargutti +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/ScrollingStackContainer/ScrollingStackContainer/Assets.xcassets/AppIcon.appiconset/Contents.json b/ScrollingStackContainer/ScrollingStackContainer/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..118c98f --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ScrollingStackContainer/ScrollingStackContainer/Base.lproj/LaunchScreen.storyboard b/ScrollingStackContainer/ScrollingStackContainer/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..fdf3f97 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer/Base.lproj/Main.storyboard b/ScrollingStackContainer/ScrollingStackContainer/Base.lproj/Main.storyboard new file mode 100644 index 0000000..c4b4b82 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/Base.lproj/Main.storyboard @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift b/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift new file mode 100644 index 0000000..3a54131 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift @@ -0,0 +1,95 @@ +// ScrollingStackController +// Efficient Scrolling Container for UIViewControllers +// +// Created by Daniele Margutti. +// Copyright © 2017 Daniele Margutti. All rights reserved. +// +// Web: http://www.danielemargutti.com +// Email: hello@danielemargutti.com +// Twitter: @danielemargutti +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import UIKit + +extension UIColor { + + static func random() -> UIColor { + let hue : CGFloat = CGFloat(arc4random() % 256) / 256 // use 256 to get full range from 0.0 to 1.0 + let saturation : CGFloat = CGFloat(arc4random() % 128) / 256 + 0.5 // from 0.5 to 1.0 to stay away from white + let brightness : CGFloat = CGFloat(arc4random() % 128) / 256 + 0.5 // from 0.5 to 1.0 to stay away from black + + return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1) + } + +} + +public class ContainerListCell: UITableViewCell { + @IBOutlet public var titleLabel: UILabel? +} + +public class ContainerList: UIViewController, StackContainable, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet public var tableView: UITableView? + + var counts: Int = 20 + var colors: [UIColor] = [] + + public static func create() -> ContainerList { + return UIStoryboard(name: "Containers", bundle: Bundle.main).instantiateViewController(withIdentifier: "ContainerList") as! ContainerList + } + + public override func viewDidLoad() { + super.viewDidLoad() + self.tableView?.delegate = self + self.tableView?.dataSource = self + + self.view.backgroundColor = UIColor.lightGray + + colors.removeAll() + for _ in 0.. Int { + return counts + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "ContainerListCell") as! ContainerListCell + + cell.titleLabel?.text = "Cell \(indexPath.row)" + cell.backgroundColor = colors[indexPath.row] + return cell + } + + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 60 + } + + public func preferredAppearanceInStack() -> ScrollingStackController.ItemAppearance { + let _ = self.view // force load of the view + return .scroll(self.tableView!, insets: UIEdgeInsetsMake(50, 0, 50, 0)) + } +} diff --git a/ScrollingStackContainer/ScrollingStackContainer/ContainerView.swift b/ScrollingStackContainer/ScrollingStackContainer/ContainerView.swift new file mode 100644 index 0000000..ca5bfc5 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/ContainerView.swift @@ -0,0 +1,47 @@ +// ScrollingStackController +// Efficient Scrolling Container for UIViewControllers +// +// Created by Daniele Margutti. +// Copyright © 2017 Daniele Margutti. All rights reserved. +// +// Web: http://www.danielemargutti.com +// Email: hello@danielemargutti.com +// Twitter: @danielemargutti +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import UIKit + +public class ContainerView: UIViewController, StackContainable { + + public static func create() -> ContainerView { + return UIStoryboard(name: "Containers", bundle: Bundle.main).instantiateViewController(withIdentifier: "ContainerView") as! ContainerView + } + + public override func viewDidLoad() { + super.viewDidLoad() + } + + public func preferredAppearanceInStack() -> ScrollingStackController.ItemAppearance { + return .view(height: 200) + } + +} diff --git a/ScrollingStackContainer/ScrollingStackContainer/ContainerView2.swift b/ScrollingStackContainer/ScrollingStackContainer/ContainerView2.swift new file mode 100644 index 0000000..38dfc09 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/ContainerView2.swift @@ -0,0 +1,47 @@ +// ScrollingStackController +// Efficient Scrolling Container for UIViewControllers +// +// Created by Daniele Margutti. +// Copyright © 2017 Daniele Margutti. All rights reserved. +// +// Web: http://www.danielemargutti.com +// Email: hello@danielemargutti.com +// Twitter: @danielemargutti +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import UIKit + +public class ContainerView2: UIViewController, StackContainable { + + public static func create() -> ContainerView2 { + return UIStoryboard(name: "Containers", bundle: Bundle.main).instantiateViewController(withIdentifier: "ContainerView2") as! ContainerView2 + } + + public override func viewDidLoad() { + super.viewDidLoad() + } + + public func preferredAppearanceInStack() -> ScrollingStackController.ItemAppearance { + return .view(height: 100) + } + +} diff --git a/ScrollingStackContainer/ScrollingStackContainer/Containers.storyboard b/ScrollingStackContainer/ScrollingStackContainer/Containers.storyboard new file mode 100644 index 0000000..b4cf099 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/Containers.storyboard @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer/Info.plist b/ScrollingStackContainer/ScrollingStackContainer/Info.plist new file mode 100644 index 0000000..09e9039 --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 0.5 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/ScrollingStackContainer/ScrollingStackContainer/ScrollingStackContainer.h b/ScrollingStackContainer/ScrollingStackContainer/ScrollingStackContainer.h new file mode 100644 index 0000000..d56146e --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/ScrollingStackContainer.h @@ -0,0 +1,19 @@ +// +// ScrollingStackContainer.h +// ScrollingStackContainer +// +// Created by daniele on 09/06/2017. +// Copyright © 2017 danielemargutti. All rights reserved. +// + +#import + +//! Project version number for ScrollingStackContainer. +FOUNDATION_EXPORT double ScrollingStackContainerVersionNumber; + +//! Project version string for ScrollingStackContainer. +FOUNDATION_EXPORT const unsigned char ScrollingStackContainerVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/ScrollingStackContainer/ScrollingStackContainer/ViewController.swift b/ScrollingStackContainer/ScrollingStackContainer/ViewController.swift new file mode 100644 index 0000000..5a6b73b --- /dev/null +++ b/ScrollingStackContainer/ScrollingStackContainer/ViewController.swift @@ -0,0 +1,61 @@ +// ScrollingStackController +// Efficient Scrolling Container for UIViewControllers +// +// Created by Daniele Margutti. +// Copyright © 2017 Daniele Margutti. All rights reserved. +// +// Web: http://www.danielemargutti.com +// Email: hello@danielemargutti.com +// Twitter: @danielemargutti +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +class ViewController: ScrollingStackController { + + var controller_1: ContainerList = ContainerList.create() + var controller_2: ContainerView = ContainerView.create() + var controller_3: ContainerView2 = ContainerView2.create() + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + self.reload() + + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + } + + func reload() { + self.viewControllers = [controller_2,controller_1,controller_3] + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + +} + diff --git a/Sources/ScrollingStackContainer/ScrollingStackController.swift b/Sources/ScrollingStackContainer/ScrollingStackController.swift new file mode 100644 index 0000000..6ddc95d --- /dev/null +++ b/Sources/ScrollingStackContainer/ScrollingStackController.swift @@ -0,0 +1,296 @@ +// ScrollingStackController +// Efficient Scrolling Container for UIViewControllers +// +// Created by Daniele Margutti. +// Copyright © 2017 Daniele Margutti. All rights reserved. +// +// Web: http://www.danielemargutti.com +// Email: hello@danielemargutti.com +// Twitter: @danielemargutti +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import UIKit + +private class StackItem { + + /// Managed UIViewController instance + var controller: UIViewController + /// Appearance to use when view controller is inside the ScrollingStackController instance + var appearance: ScrollingStackController.ItemAppearance + /// This is the regular rect of the item into the stack + /// Be careful: this is not the real rect due the fact it will be adjusted as the parent + /// scroll in order to keep healthy memory usage. + var rect: CGRect = .zero + + /// Initialize a new stack item to manage a view controller instance + /// + /// - Parameters: + /// - controller: view controller instance to manage + /// - appearance: appearance to set when contained in a scrolling stack + init(_ controller: UIViewController, _ appearance: ScrollingStackController.ItemAppearance) { + self.controller = controller + self.appearance = appearance + } +} + +extension UIView { + + /// Helper method to get the first height constraint set for an instance of UIView + public var heigthConstraint: NSLayoutConstraint? { + return self.constraints.first(where: { $0.firstAttribute == .height }) + } + +} + +/// We cannot add an extension to UIViewController and allows to override the +/// default implementation in UIViewController subclasses. So be sure to +/// use StackContainable only for UIViewController until a new Swift version +/// allows these stuff. +public protocol StackContainable: class { + + /// You should implement it in your UIViewController subclass in order + /// to specify how it must appear when contained in a ScrollingStackContainer. + /// Default implementation is specified below. + /// + /// - Returns: appearance + func preferredAppearanceInStack() -> ScrollingStackController.ItemAppearance + +} + +extension StackContainable where Self: UIViewController { + + /// You must override this method if you need to specify a custom appearance of the view + /// controller instance when contained in a ScrollingStackController. + /// By default each view controller's view is rendered as a fixed height view. + /// Height is calculated automatically in this order: + /// - attempt to use optional auto-layout height constraint + /// - attempt to use preferredContentSize value + /// - attempt to use view's frame.height + /// + /// + /// - Returns: Appearance of the controller when contained a ScrollingStackController + public func preferredAppearanceInStack() -> ScrollingStackController.ItemAppearance { + // Search for intrinsic height constraint + var height_constraint = self.view.heigthConstraint?.constant ?? 0 + // Attempt to use the preferredContentSize height + if height_constraint == 0 { + height_constraint = self.preferredContentSize.height + } + // Attempt to use view's height + if height_constraint == 0 { + height_constraint = self.view.frame.size.height + } + guard height_constraint > 0 else { + print("ViewController \(self) does not specify a valid height when contained in stack") + return .view(height: height_constraint) + } + return .view(height: height_constraint) + } +} + +public class ScrollingStackController: UIViewController, UIScrollViewDelegate { + + /// This define the behaviour stack needs to keep for a specified controller + /// + /// - view: fixed height view. You should use it only for view which does not contains scrolling data + /// - scroll: use it when your view controller contains an UIScrollView subclass. Specify it as paramter + /// along with optional edge insets from the superview. + public enum ItemAppearance { + case view(height: CGFloat) + case scroll(_: UIScrollView, insets: UIEdgeInsets) + } + + /// This is the parent scroll view. Be sure to connect it to a valid object + @IBOutlet public var scrollView: UIScrollView? + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + guard let scroll = self.scrollView else { // you must to create a valid scroll view + fatalError("You must connect a valid scroll view to the view controller") + } + scroll.translatesAutoresizingMaskIntoConstraints = false + scroll.delegate = self + } + + /// Reference to stacked items + private var items: [StackItem] = [] + + /// Use this property to set the ordered list of UIViewController instances + /// you want to set into the stack view + public var viewControllers: [StackContainable] { + set { + self.removeAllViewControllers() + self.items = newValue.map { StackItem($0 as! UIViewController, $0.preferredAppearanceInStack() ) } + self.relayoutItems() + } + get { return self.items.map { $0.controller as! StackContainable } } + } + + /// Adjust stacked items as the view did scroll + /// + /// - Parameter scrollView: scrollview + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.adjustContentOnScroll() + } + + /// This function remove all view controllers from the stack + private func removeAllViewControllers() { + self.items.forEach { + $0.controller.removeFromParentViewController() + $0.controller.view.removeFromSuperview() + } + self.items.removeAll() + } + + /// Adjust layout as the parent view's change + public override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + self.relayoutItems() + } + + /// Return the visible portion of the scrolling stack scroll view + private var visibleRect: CGRect { + get { + return CGRect(x: 0.0, + y: scrollView!.contentOffset.y, + width: scrollView!.frame.size.width, + height: scrollView!.frame.size.height) + } + } + + /// This function is used to calculate the rect of each item into the stack + /// and put it in place. It's called when a new array of items is set. + public func relayoutItems() { + var offset_y: CGFloat = 0.0 + let width = self.scrollView!.frame.size.width + + for item in self.items { + var itemHeight: CGFloat = 0.0 + + switch item.appearance { + case .scroll(let scrollView, let insets): + // for UIViewController with table/collections/scrollview inside + // the occupied space is calculated with the content size of scroll + // itself and specified inset of it inside the parent view. + itemHeight = scrollView.contentSize.height + itemHeight += insets.top + insets.bottom // take care of the insets + break + case .view(let height): + // for standard UIView it uses provided height + itemHeight = height + } + + // This is the ideal rect + item.rect = CGRect(x: 0.0, y: offset_y, width: width, height: itemHeight) + item.controller.view.frame = item.rect // don't worry, its adjusted below + // add the view in place + self.scrollView!.addSubview(item.controller.view) + offset_y += itemHeight // calculate the new offset + } + // Setup manyally the content size and adjust the items based upon the visibility + self.scrollView!.contentSize = CGSize(width: width, height: offset_y) + self.adjustContentOnScroll() + } + + + /// This function is used to adjust the frame of the object as the parent + /// scroll view did scroll. + /// How it works: + /// - Standard UIViewController's with `view` appearance are showed as is. No changes are + /// applied to the frame of the view itself. + /// - UIViewController with `scroll` appearance are managed by adjusting the specified + /// scrollview's offset and frame in order to take care of the visibility region into the + /// parent scroll view. + /// When scroll reaches the top of the scrollview it's pinned on top and the offset is adjusted + /// on scroll in order to simulate a continous scrolling of the parent scrollview. + /// The frame of the inner scroll is adjusted in order to occupy at the max the entire region + /// of the parent, and when partially visible, only the visible region. + //// In this way we can maximize the memory usage by using table/collection's caching architecture. + private func adjustContentOnScroll() { + let scrollView = self.scrollView! + let contentOffset = scrollView.contentOffset + + // This is the visible rect of the parent scroll view + let visibleRect = self.visibleRect + // This is the current offset into parent scroll view + let mainOffsetY = contentOffset.y + + let w = scrollView.frame.size.width + + // Enumerate each item of the stack + for item in self.items { + let itemRect = item.rect // get the ideal rect (occupied space) + switch item.appearance { + case .view(_): + // Standard UIView are ignored + break + case .scroll(let innerScroll, let insets): + // A special consideration is made for scroll views + innerScroll.isScrollEnabled = false // disable scrolling so it does not interfere with the parent scroll + + // evaluate the visible region in parent + let itemVisibleRect = visibleRect.intersection(itemRect) + + if itemVisibleRect.height == 0.0 { + // If not visible the frame of the inner's scroll is canceled + // No cells are rendered until the item became partially visible + innerScroll.frame = CGRect.zero + } else { + // The item is partially visible + if mainOffsetY > (itemRect.minY + insets.bottom) { + // If during scrolling the inner table/collection has reached the top + // of the parent scrollview it will be pinned on top + + // This calculate the offset reached while scrolling the inner scroll + // It's used to adjust the inner table/collection offset in order to + // simulate continous scrolling + let innerScrollOffsetY = mainOffsetY - itemRect.minY - insets.top + // This is the height of the visible region of the inner table/collection + let visibleInnerHeight = innerScroll.contentSize.height - innerScrollOffsetY + + var innerScrollRect = CGRect.zero + innerScrollRect.origin = CGPoint(x: 0, y: innerScrollOffsetY + insets.top) + if visibleInnerHeight < visibleRect.size.height { + // partially visible when pinned on top + innerScrollRect.size = CGSize(width: w, height: min(visibleInnerHeight,itemVisibleRect.height)) + } else { + // the inner scroll occupy the entire parent scroll's height + innerScrollRect.size = itemVisibleRect.size + } + innerScroll.frame = innerScrollRect + // adjust the offset to simulate the scroll + innerScroll.contentOffset = CGPoint(x: 0, y: innerScrollOffsetY) + } else { + // The inner scroll view is partially visible + // Adjust the frame as it needs (at its max it reaches the height of the parent) + let offsetOfInnerY = (itemRect.minY + insets.top) - mainOffsetY + let visibileHeight = visibleRect.size.height - offsetOfInnerY + + innerScroll.frame = CGRect(x: 0, y: insets.top, width: w, height: visibileHeight) + innerScroll.contentOffset = CGPoint.zero + } + } + } + } + } + +} diff --git a/example.gif b/example.gif new file mode 100644 index 0000000..302c6d8 Binary files /dev/null and b/example.gif differ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..ca717e4 Binary files /dev/null and b/logo.png differ