Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Example/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Library Access</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
Expand All @@ -24,6 +22,8 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Library Access</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand All @@ -35,6 +35,7 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>
4 changes: 2 additions & 2 deletions SeedStackViewController.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "SeedStackViewController"
s.module_name = "StackViewController"
s.version = "0.5.3"
s.version = "0.6.0"
s.summary = "Simplifies the process of building forms and other static content using UIStackView."
s.description = "StackViewController is a Swift framework that simplifies the process of building forms and other static content using UIStackView."
s.homepage = "https://github.com/seedco/StackViewController"
Expand All @@ -11,7 +11,7 @@ Pod::Spec.new do |s|
git: "https://github.com/seedco/StackViewController.git",
tag: s.version.to_s
}
s.ios.deployment_target = "9.0"
s.ios.deployment_target = "12.0"
s.source_files = "StackViewController/*.{h,swift}"
s.frameworks = "UIKit"
s.swift_versions = "5.0"
Expand Down
18 changes: 13 additions & 5 deletions StackViewController.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
72DE37271CCDC769009CAE32 /* ImageAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72DE37261CCDC769009CAE32 /* ImageAttachmentViewController.swift */; };
72DE37291CCDCCB6009CAE32 /* UIStackViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72DE37281CCDCCB6009CAE32 /* UIStackViewExtensions.swift */; };
72DE372B1CCDD998009CAE32 /* ImageThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72DE372A1CCDD998009CAE32 /* ImageThumbnailView.swift */; };
756AE907270B9DA000644AC5 /* ContentWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 756AE906270B9DA000644AC5 /* ContentWidth.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -88,6 +89,7 @@
72DE37261CCDC769009CAE32 /* ImageAttachmentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageAttachmentViewController.swift; sourceTree = "<group>"; };
72DE37281CCDCCB6009CAE32 /* UIStackViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIStackViewExtensions.swift; sourceTree = "<group>"; };
72DE372A1CCDD998009CAE32 /* ImageThumbnailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageThumbnailView.swift; sourceTree = "<group>"; };
756AE906270B9DA000644AC5 /* ContentWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWidth.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -144,6 +146,7 @@
children = (
72D193E61CC3576A00645F83 /* StackViewController.h */,
72D193FB1CC3577B00645F83 /* AutoScrollView.swift */,
756AE906270B9DA000644AC5 /* ContentWidth.swift */,
72D193FD1CC3577B00645F83 /* SeparatorView.swift */,
72D193FE1CC3577B00645F83 /* StackViewContainer.swift */,
72D193FF1CC3577B00645F83 /* StackViewController.swift */,
Expand Down Expand Up @@ -258,7 +261,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 1110;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "Seed Platform, Inc";
TargetAttributes = {
72D193E31CC3576A00645F83 = {
Expand Down Expand Up @@ -326,6 +329,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
756AE907270B9DA000644AC5 /* ContentWidth.swift in Sources */,
72DE37211CCDABA8009CAE32 /* UIViewExtensions.swift in Sources */,
72D194031CC3577B00645F83 /* SeparatorView.swift in Sources */,
72DE37291CCDCCB6009CAE32 /* UIStackViewExtensions.swift in Sources */,
Expand Down Expand Up @@ -410,6 +414,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand All @@ -434,7 +439,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -466,6 +471,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand All @@ -484,7 +490,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
Expand All @@ -504,7 +510,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = StackViewController/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = co.seed.StackViewController;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -529,7 +535,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = StackViewController/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = co.seed.StackViewController;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -573,6 +579,7 @@
PRODUCT_BUNDLE_IDENTIFIER = co.seed.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
Expand All @@ -586,6 +593,7 @@
PRODUCT_BUNDLE_IDENTIFIER = co.seed.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1110"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1110"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
101 changes: 98 additions & 3 deletions StackViewController/AutoScrollView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,121 @@ open class AutoScrollView: UIScrollView {
}
didSet {
if let contentView = contentView {
contentView.translatesAutoresizingMaskIntoConstraints = false
addSubview(contentView)
updateContentViewConstraints()
}
}
}
fileprivate var contentViewConstraints: [NSLayoutConstraint]?

/// This layout guide follows the `contentView` and honors the `horizontallyCompactContentWidth`
/// and `horizontallyRegularContentWidth` values.
public var contentViewLayoutGuide = UILayoutGuide()
private var contentViewLayoutGuideConstraints: [NSLayoutConstraint] = []

/// This setting determines how the content should be laid out in a horizontally compact environment.
public var horizontallyCompactContentWidth: ContentWidth = .matchScrollViewWidth {
didSet {
guard horizontallyCompactContentWidth != oldValue else { return }

switch traitCollection.horizontalSizeClass {
case .compact: updateContentViewConstraints()
case .regular, .unspecified: break // no-op
@unknown default: break
}
}
}

/// This setting determines how the content should be laid out in a horizontally regular environment.
public var horizontallyRegularContentWidth: ContentWidth = .matchScrollViewWidth {
didSet {
guard horizontallyRegularContentWidth != oldValue else { return }

switch traitCollection.horizontalSizeClass {
case .regular: updateContentViewConstraints()
case .compact, .unspecified: break // no-op
@unknown default: break
}
}
}

override open var contentInset: UIEdgeInsets {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should insets vary by size class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually ran into problems with the contentInset. In the example app, there is a StackViewController inside another StackViewController. When the size class changed, the contentInset for the scroll view for the child StackViewController was reset to zero. The best I could figure out was that UIKit was reseting the contentInset. Even when I resized the window to be back to its original size, it was at zero. I put breakpoints in the didSet and it never hit the breakpoint. I spent a good amount of time trying to figure it out. I never did figure it out. I decided to move on since I had already spent so much time on it and it wasn't made worse w/ my changes. It had always been like that, we just never noticed because we weren't changing the window size.

Note: I never tested to see if this was a problem with the outer stack VC/scroll view. I don't think it ever had a contentInset so if the same thing happens there, it never would have surfaced there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All that being said, I can see how different insets for each size class would be beneficial. We don't need it and as far as I know there haven't been any requests for it though.

Since you gave the approval, I'm going to assume it's good to merge as is. If you'd like me to work on any changes to the contentInset, let me know and I can work on it in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last note @louoso, if the content inset issue I mentioned above seems worth fixing to you, I have ideas on how to fix it. As far as I know, the bug as it exists now shouldn't actually impact Formalist or our own apps because I don't think we make use of contentInset.

didSet {
updateContentViewConstraints()
}
}

fileprivate func updateContentViewConstraints() {
let contentViewFollowsReadableWidth: Bool
switch traitCollection.horizontalSizeClass {
case .compact:
switch horizontallyCompactContentWidth {
case .matchScrollViewWidth:
contentViewFollowsReadableWidth = false
case .matchReadableContentGuideWidth:
contentViewFollowsReadableWidth = true
}

case .regular:
switch horizontallyRegularContentWidth {
case .matchScrollViewWidth:
contentViewFollowsReadableWidth = false
case .matchReadableContentGuideWidth:
contentViewFollowsReadableWidth = true
}

case .unspecified:
return // abort early

@unknown default:
contentViewFollowsReadableWidth = false
}

if let constraints = contentViewConstraints {
NSLayoutConstraint.deactivate(constraints)
}
if let contentView = contentView {
contentViewConstraints = contentView.activateSuperviewHuggingConstraints(insets: contentInset)
NSLayoutConstraint.deactivate(contentViewLayoutGuideConstraints)

let newLayoutGuide: UILayoutGuide
if contentViewFollowsReadableWidth, let contentView = contentView {
let newConstraints = [
contentView.leadingAnchor.constraint(
equalTo: readableContentGuide.leadingAnchor,
constant: contentInset.left
),
contentView.trailingAnchor.constraint(
equalTo: readableContentGuide.trailingAnchor,
constant: -contentInset.right
),
contentView.topAnchor.constraint(equalTo: topAnchor, constant: contentInset.top),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -contentInset.bottom),
]
NSLayoutConstraint.activate(newConstraints)
contentViewConstraints = newConstraints

newLayoutGuide = readableContentGuide
} else {
contentViewConstraints = nil
contentViewConstraints = contentView?.activateSuperviewHuggingConstraints(insets: contentInset)

newLayoutGuide = frameLayoutGuide
}

contentViewLayoutGuideConstraints = [
contentViewLayoutGuide.leadingAnchor.constraint(equalTo: newLayoutGuide.leadingAnchor),
contentViewLayoutGuide.topAnchor.constraint(equalTo: newLayoutGuide.topAnchor),
contentViewLayoutGuide.trailingAnchor.constraint(equalTo: newLayoutGuide.trailingAnchor),
contentViewLayoutGuide.bottomAnchor.constraint(equalTo: newLayoutGuide.bottomAnchor),
]
NSLayoutConstraint.activate(contentViewLayoutGuideConstraints)
}

fileprivate func commonInit() {
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(AutoScrollView.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
nc.addObserver(self, selector: #selector(AutoScrollView.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)

addLayoutGuide(contentViewLayoutGuide)
}

public override init(frame: CGRect) {
Expand All @@ -77,6 +164,14 @@ open class AutoScrollView: UIScrollView {
return true
}

open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
guard traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass else {
return
}

updateContentViewConstraints()
}

// MARK: Notifications

// Implementation based on code from Apple documentation
Expand Down
16 changes: 16 additions & 0 deletions StackViewController/ContentWidth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// ContentWidth.swift
// StackViewController
//
// Created by Devin McKaskle on 10/4/21.
// Copyright © 2021 Seed Platform, Inc. All rights reserved.
//

@objc public enum ContentWidth: Int {
case matchScrollViewWidth
case matchReadableContentGuideWidth

public mutating func toggle() {
self = ContentWidth(rawValue: rawValue + 1) ?? .matchScrollViewWidth
}
}
2 changes: 1 addition & 1 deletion StackViewController/StackViewContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ open class StackViewContainer: UIView, UIScrollViewDelegate {
}
}()
stackViewSizeConstraint =
NSLayoutConstraint(item: stackView, attribute: attribute, relatedBy: .equal, toItem: scrollView, attribute: attribute, multiplier: 1.0, constant: 0.0)
NSLayoutConstraint(item: stackView, attribute: attribute, relatedBy: .equal, toItem: scrollView.contentViewLayoutGuide, attribute: attribute, multiplier: 1.0, constant: 0.0)
stackViewSizeConstraint?.isActive = true
}

Expand Down
12 changes: 12 additions & 0 deletions StackViewController/StackViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ open class StackViewController: UIViewController {
open var scrollView: UIScrollView {
return stackViewContainer.scrollView
}

/// This setting determines how the content should be laid out in a horizontally compact environment.
public var horizontallyCompactContentWidth: ContentWidth {
get { stackViewContainer.scrollView.horizontallyCompactContentWidth }
set { stackViewContainer.scrollView.horizontallyCompactContentWidth = newValue }
}

/// This setting determines how the content should be laid out in a horizontally regular environment.
public var horizontallyRegularContentWidth: ContentWidth {
get { stackViewContainer.scrollView.horizontallyRegularContentWidth }
set { stackViewContainer.scrollView.horizontallyRegularContentWidth = newValue }
}

private lazy var stackViewContainer = StackViewContainer()

Expand Down