From fca4b63cc6011c6594829ea8ea1b4dc5bb9820e0 Mon Sep 17 00:00:00 2001 From: Aaron Vegh Date: Thu, 25 Oct 2018 09:56:20 -0400 Subject: [PATCH 1/5] Update to support Swift 4.2 --- .gitignore | 2 + .../ScrollingStackController.swift | 551 +++++++++--------- 2 files changed, 289 insertions(+), 264 deletions(-) diff --git a/.gitignore b/.gitignore index e43b0f9..dd3eab3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .DS_Store +xcshareddata +xcuserdata diff --git a/Sources/ScrollingStackContainer/ScrollingStackController.swift b/Sources/ScrollingStackContainer/ScrollingStackController.swift index 6ddc95d..98f7291 100644 --- a/Sources/ScrollingStackContainer/ScrollingStackController.swift +++ b/Sources/ScrollingStackContainer/ScrollingStackController.swift @@ -4,61 +4,67 @@ // Created by Daniele Margutti. // Copyright © 2017 Daniele Margutti. All rights reserved. // -// Web: http://www.danielemargutti.com -// Email: hello@danielemargutti.com -// Twitter: @danielemargutti +// 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: +// 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 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. +// 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 - } + + /// Managed UIViewController instance + var controller: StackContainable + /// 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: StackContainable, _ appearance: ScrollingStackController.ItemAppearance) { + self.controller = controller + self.appearance = appearance + } +} + +extension StackItem { + var value: UIViewController? { + return self.controller as? UIViewController + } } 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 }) - } - + + /// 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 @@ -66,231 +72,248 @@ extension UIView { /// 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 - + + /// 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) - } + + /// 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 - } - } - } - } - } - + + /// 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.compactMap { + if let vc = $0 as? UIViewController { + addChild(vc) + } + return StackItem($0, $0.preferredAppearanceInStack() ) + } + self.relayoutItems() + } + get { + return self.items.compactMap { + guard let vc = $0.value as? StackContainable else { return nil } + return vc + } + } + } + + public func resetAppearance() { + self.items = viewControllers.compactMap { + return StackItem($0, $0.preferredAppearanceInStack() ) + } + self.relayoutItems() + } + + /// 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.value?.removeFromParent() + $0.value?.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 { + 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 + 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.value?.view.frame = item.rect // don't worry, its adjusted below + // add the view in place + if let controller = item.value { + self.scrollView!.addSubview(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 + } + } + } + } + } + } From 7b0e7028331bdbbaa3d061ca7c0994a6ce12637e Mon Sep 17 00:00:00 2001 From: Aaron Vegh Date: Tue, 30 Oct 2018 16:06:16 -0400 Subject: [PATCH 2/5] Updates for Swift 4.2; use leading and trailing margin values in calculations --- .../project.pbxproj | 37 ++++++++++++++----- .../ScrollingStackContainer.xcscheme | 2 +- .../ScrollingStackContainer/AppDelegate.swift | 2 +- .../ContainerList.swift | 2 +- .../ScrollingStackController.swift | 21 ++++++----- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj index 5831fe9..68f6132 100644 --- a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/project.pbxproj @@ -204,7 +204,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0830; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = danielemargutti; TargetAttributes = { 64CB31451EEA011900D88EAF = { @@ -214,7 +214,7 @@ }; 64E84F811EE9F7D600EC8295 = { CreatedOnToolsVersion = 8.3.3; - DevelopmentTeam = E5DU3FA699; + DevelopmentTeam = 36Q79374L3; ProvisioningStyle = Automatic; }; }; @@ -328,7 +328,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -352,7 +352,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -369,15 +369,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -419,15 +427,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -457,12 +473,12 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = E5DU3FA699; + DEVELOPMENT_TEAM = 36Q79374L3; INFOPLIST_FILE = ScrollingStackContainer/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; + PRODUCT_BUNDLE_IDENTIFIER = com.innoveghtive.ScrollingStackContainer; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -471,12 +487,12 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = E5DU3FA699; + DEVELOPMENT_TEAM = 36Q79374L3; INFOPLIST_FILE = ScrollingStackContainer/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.ScrollingStackContainer; + PRODUCT_BUNDLE_IDENTIFIER = com.innoveghtive.ScrollingStackContainer; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -490,6 +506,7 @@ 64CB31511EEA011900D88EAF /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 64E84F7D1EE9F7D600EC8295 /* Build configuration list for PBXProject "ScrollingStackContainer" */ = { isa = XCConfigurationList; diff --git a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme index af69e54..bdb6b0b 100644 --- a/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme +++ b/ScrollingStackContainer/ScrollingStackContainer.xcodeproj/xcshareddata/xcschemes/ScrollingStackContainer.xcscheme @@ -1,6 +1,6 @@ Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } diff --git a/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift b/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift index 3a54131..b353aef 100644 --- a/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift +++ b/ScrollingStackContainer/ScrollingStackContainer/ContainerList.swift @@ -90,6 +90,6 @@ public class ContainerList: UIViewController, StackContainable, UITableViewDataS public func preferredAppearanceInStack() -> ScrollingStackController.ItemAppearance { let _ = self.view // force load of the view - return .scroll(self.tableView!, insets: UIEdgeInsetsMake(50, 0, 50, 0)) + return .scroll(self.tableView!, insets: UIEdgeInsets(top: 50, left: 0, bottom: 50, right: 0)) } } diff --git a/Sources/ScrollingStackContainer/ScrollingStackController.swift b/Sources/ScrollingStackContainer/ScrollingStackController.swift index 98f7291..d7dadb4 100644 --- a/Sources/ScrollingStackContainer/ScrollingStackController.swift +++ b/Sources/ScrollingStackContainer/ScrollingStackController.swift @@ -192,10 +192,11 @@ public class ScrollingStackController: UIViewController, UIScrollViewDelegate { /// Return the visible portion of the scrolling stack scroll view private var visibleRect: CGRect { + guard let scrollView = scrollView else { return .zero } return CGRect(x: 0.0, - y: scrollView!.contentOffset.y, - width: scrollView!.frame.size.width, - height: scrollView!.frame.size.height) + y: scrollView.contentOffset.y, + width: scrollView.frame.size.width, + height: scrollView.frame.size.height) } @@ -278,7 +279,7 @@ public class ScrollingStackController: UIViewController, UIScrollViewDelegate { // No cells are rendered until the item became partially visible innerScroll.frame = CGRect.zero } else { - // The item is partially visible + // The item is at least 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 @@ -291,13 +292,13 @@ public class ScrollingStackController: UIViewController, UIScrollViewDelegate { let visibleInnerHeight = innerScroll.contentSize.height - innerScrollOffsetY var innerScrollRect = CGRect.zero - innerScrollRect.origin = CGPoint(x: 0, y: innerScrollOffsetY + insets.top) + innerScrollRect.origin = CGPoint(x: insets.left, 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)) + innerScrollRect.size = CGSize(width: w - (insets.left + insets.right), height: min(visibleInnerHeight, itemVisibleRect.height)) } else { // the inner scroll occupy the entire parent scroll's height - innerScrollRect.size = itemVisibleRect.size + innerScrollRect.size = CGSize(width: itemVisibleRect.size.width - (insets.left + insets.right), height: itemVisibleRect.height - insets.top) } innerScroll.frame = innerScrollRect // adjust the offset to simulate the scroll @@ -305,10 +306,10 @@ public class ScrollingStackController: UIViewController, UIScrollViewDelegate { } 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 + let offsetOfInnerY = (itemRect.minY + insets.top + insets.bottom) - mainOffsetY + let visibleHeight = visibleRect.size.height - offsetOfInnerY - innerScroll.frame = CGRect(x: 0, y: insets.top, width: w, height: visibileHeight) + innerScroll.frame = CGRect(x: insets.left, y: insets.top, width: w - (insets.left + insets.right), height: visibleHeight) innerScroll.contentOffset = CGPoint.zero } } From bbb8f1d7e9547326fdfca998b822572cd9805740 Mon Sep 17 00:00:00 2001 From: Aaron Vegh Date: Tue, 30 Oct 2018 16:25:52 -0400 Subject: [PATCH 3/5] Declare class as open --- Sources/ScrollingStackContainer/ScrollingStackController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ScrollingStackContainer/ScrollingStackController.swift b/Sources/ScrollingStackContainer/ScrollingStackController.swift index d7dadb4..0a64196 100644 --- a/Sources/ScrollingStackContainer/ScrollingStackController.swift +++ b/Sources/ScrollingStackContainer/ScrollingStackController.swift @@ -113,7 +113,7 @@ extension StackContainable where Self: UIViewController { } } -public class ScrollingStackController: UIViewController, UIScrollViewDelegate { +open class ScrollingStackController: UIViewController, UIScrollViewDelegate { /// This define the behaviour stack needs to keep for a specified controller /// From 7cb598e40efca320f534b4d5ba7517f16c4aa31b Mon Sep 17 00:00:00 2001 From: Aaron Vegh Date: Tue, 30 Oct 2018 16:29:55 -0400 Subject: [PATCH 4/5] More open declarations --- .../ScrollingStackContainer/ScrollingStackController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ScrollingStackContainer/ScrollingStackController.swift b/Sources/ScrollingStackContainer/ScrollingStackController.swift index 0a64196..13a6914 100644 --- a/Sources/ScrollingStackContainer/ScrollingStackController.swift +++ b/Sources/ScrollingStackContainer/ScrollingStackController.swift @@ -128,7 +128,7 @@ open class ScrollingStackController: UIViewController, UIScrollViewDelegate { /// 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) { + open 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") @@ -185,7 +185,7 @@ open class ScrollingStackController: UIViewController, UIScrollViewDelegate { } /// Adjust layout as the parent view's change - public override func viewDidLayoutSubviews() { + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() self.relayoutItems() } From d194717f30fb00d2a758eb1c6ce2c30beac3e969 Mon Sep 17 00:00:00 2001 From: Aaron Vegh Date: Fri, 4 Jan 2019 11:11:03 -0500 Subject: [PATCH 5/5] Guard against the scrollview being nil --- .../ScrollingStackController.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/ScrollingStackContainer/ScrollingStackController.swift b/Sources/ScrollingStackContainer/ScrollingStackController.swift index 13a6914..e3b92eb 100644 --- a/Sources/ScrollingStackContainer/ScrollingStackController.swift +++ b/Sources/ScrollingStackContainer/ScrollingStackController.swift @@ -203,8 +203,9 @@ open class ScrollingStackController: UIViewController, UIScrollViewDelegate { /// 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() { + guard let scrollView = self.scrollView else { return } var offset_y: CGFloat = 0.0 - let width = self.scrollView!.frame.size.width + let width = scrollView.frame.size.width for item in self.items { var itemHeight: CGFloat = 0.0 @@ -226,14 +227,15 @@ open class ScrollingStackController: UIViewController, UIScrollViewDelegate { item.value?.view.frame = item.rect // don't worry, its adjusted below // add the view in place if let controller = item.value { - self.scrollView!.addSubview(controller.view) + scrollView.addSubview(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) + scrollView.contentSize = CGSize(width: width, height: offset_y) self.adjustContentOnScroll() } + /// This function is used to adjust the frame of the object as the parent @@ -250,7 +252,7 @@ open class ScrollingStackController: UIViewController, UIScrollViewDelegate { /// 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! + guard let scrollView = self.scrollView else { return } let contentOffset = scrollView.contentOffset // This is the visible rect of the parent scroll view @@ -306,7 +308,7 @@ open class ScrollingStackController: UIViewController, UIScrollViewDelegate { } 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 + insets.bottom) - mainOffsetY + let offsetOfInnerY = (itemRect.minY + insets.top) - mainOffsetY let visibleHeight = visibleRect.size.height - offsetOfInnerY innerScroll.frame = CGRect(x: insets.left, y: insets.top, width: w - (insets.left + insets.right), height: visibleHeight)