Skip to content

Commit

Permalink
Merge pull request #216 from antoinelamy/feature/AutoSizing
Browse files Browse the repository at this point in the history
Add automatic sizeThatFits computation for views
  • Loading branch information
lucdion authored Jun 30, 2020
2 parents 930593a + b702f19 commit 21e53ef
Show file tree
Hide file tree
Showing 19 changed files with 456 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode11.2
osx_image: xcode11.5

cache:
- bundler
Expand Down
32 changes: 28 additions & 4 deletions Example/PinLayoutSample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@
24F246141FA8D57100B6332E /* UIImageView+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F246131FA8D57100B6332E /* UIImageView+Download.swift */; };
24F75B5B1EE5644E008DB567 /* IntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F75B591EE5644E008DB567 /* IntroView.swift */; };
24F75B5C1EE5644E008DB567 /* IntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F75B5A1EE5644E008DB567 /* IntroViewController.swift */; };
DE6C3D736B571B80E207DF6A /* Pods_PinLayoutSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD69688AA2A3F0994F3074E /* Pods_PinLayoutSample.framework */; };
C892FA1924A5821E0086A75E /* AutoSizingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1824A5821E0086A75E /* AutoSizingViewController.swift */; };
C892FA1B24A5822B0086A75E /* AutoSizingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1A24A5822B0086A75E /* AutoSizingView.swift */; };
C892FA1D24A584010086A75E /* ContentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1C24A584010086A75E /* ContentService.swift */; };
C892FA1F24A597FA0086A75E /* AutoSizingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1E24A597FA0086A75E /* AutoSizingContainerView.swift */; };
C892FA2124A598170086A75E /* ProxyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA2024A598170086A75E /* ProxyWrapper.swift */; };
DF390898211900320049FD56 /* AnimationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF390897211900320049FD56 /* AnimationsView.swift */; };
DF39089A211900480049FD56 /* AnimationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF390899211900480049FD56 /* AnimationsViewController.swift */; };
DF4C1AA4205AEDFC00DED50B /* SafeAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4C1AA0205AEDFC00DED50B /* SafeAreaView.swift */; };
Expand Down Expand Up @@ -148,8 +152,12 @@
24F75B591EE5644E008DB567 /* IntroView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroView.swift; sourceTree = "<group>"; };
24F75B5A1EE5644E008DB567 /* IntroViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroViewController.swift; sourceTree = "<group>"; };
A35A00E6536E49A548E763E6 /* Pods-PinLayoutSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PinLayoutSample.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-PinLayoutSample/Pods-PinLayoutSample.debug.xcconfig"; sourceTree = "<group>"; };
AAD69688AA2A3F0994F3074E /* Pods_PinLayoutSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PinLayoutSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C589624E868FCB20F7C10918 /* Pods-PinLayoutSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PinLayoutSample.release.xcconfig"; path = "../Pods/Target Support Files/Pods-PinLayoutSample/Pods-PinLayoutSample.release.xcconfig"; sourceTree = "<group>"; };
C892FA1824A5821E0086A75E /* AutoSizingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingViewController.swift; sourceTree = "<group>"; };
C892FA1A24A5822B0086A75E /* AutoSizingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingView.swift; sourceTree = "<group>"; };
C892FA1C24A584010086A75E /* ContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentService.swift; sourceTree = "<group>"; };
C892FA1E24A597FA0086A75E /* AutoSizingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingContainerView.swift; sourceTree = "<group>"; };
C892FA2024A598170086A75E /* ProxyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyWrapper.swift; sourceTree = "<group>"; };
DF390897211900320049FD56 /* AnimationsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimationsView.swift; path = PinLayoutSample/UI/Examples/Animations/AnimationsView.swift; sourceTree = SOURCE_ROOT; };
DF390899211900480049FD56 /* AnimationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationsViewController.swift; sourceTree = "<group>"; };
DF4C1AA0205AEDFC00DED50B /* SafeAreaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeAreaView.swift; sourceTree = "<group>"; };
Expand All @@ -173,7 +181,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DE6C3D736B571B80E207DF6A /* Pods_PinLayoutSample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -183,7 +190,6 @@
160FB83905049FCEDD18DC8A /* Frameworks */ = {
isa = PBXGroup;
children = (
AAD69688AA2A3F0994F3074E /* Pods_PinLayoutSample.framework */,
246812FC1F8D013500462E53 /* NotificationCenter.framework */,
);
name = Frameworks;
Expand Down Expand Up @@ -240,6 +246,7 @@
DFD31B9D212EE4D600566CA4 /* TableViewReadableContent */,
24D18D181F3DECD6008129EF /* IntroRTL */,
2416376F1F8E4BC200EE703A /* IntroObjectiveC */,
C892FA1124A582050086A75E /* AutoSizing */,
24CD1E8D1F8E4B0A00C3A54D /* PinLayoutSample-Bridging-Header.h */,
);
path = Examples;
Expand All @@ -255,6 +262,8 @@
24F246131FA8D57100B6332E /* UIImageView+Download.swift */,
DFD27840211B1A700056BD93 /* UINavigationController+Orientation.swift */,
DFD27847211B1D090056BD93 /* UITabBarController+Orientation.swift */,
C892FA1C24A584010086A75E /* ContentService.swift */,
C892FA2024A598170086A75E /* ProxyWrapper.swift */,
);
path = Common;
sourceTree = "<group>";
Expand Down Expand Up @@ -399,6 +408,16 @@
path = Intro;
sourceTree = "<group>";
};
C892FA1124A582050086A75E /* AutoSizing */ = {
isa = PBXGroup;
children = (
C892FA1824A5821E0086A75E /* AutoSizingViewController.swift */,
C892FA1A24A5822B0086A75E /* AutoSizingView.swift */,
C892FA1E24A597FA0086A75E /* AutoSizingContainerView.swift */,
);
path = AutoSizing;
sourceTree = "<group>";
};
DF3908912118FFF20049FD56 /* Animations */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -661,10 +680,12 @@
24F75B5C1EE5644E008DB567 /* IntroViewController.swift in Sources */,
241637741F8E4BC200EE703A /* IntroObjectiveCViewController.m in Sources */,
DF390898211900320049FD56 /* AnimationsView.swift in Sources */,
C892FA2124A598170086A75E /* ProxyWrapper.swift in Sources */,
2439CC541E665C6B003326FB /* RelativeView.swift in Sources */,
2439CC551E665C6B003326FB /* RelativeViewController.swift in Sources */,
247157941F87BD680003424F /* UIEdgeInsets+PinLayout.swift in Sources */,
24D18D1E1F3DED0D008129EF /* IntroRTLViewController.swift in Sources */,
C892FA1F24A597FA0086A75E /* AutoSizingContainerView.swift in Sources */,
DFEAF74C20C9649F00E33147 /* WrapContentViewController.swift in Sources */,
24F246121FA8D4D100B6332E /* HouseCell.swift in Sources */,
DF4C1AAE205AF78A00DED50B /* SafeAreaAndMarginsViewController.swift in Sources */,
Expand All @@ -681,6 +702,7 @@
24CB999C1F29059B004EA7FB /* AdjustToContainerView.swift in Sources */,
2497CFED1EF40B9100DFD13B /* FormView.swift in Sources */,
24F75B5B1EE5644E008DB567 /* IntroView.swift in Sources */,
C892FA1B24A5822B0086A75E /* AutoSizingView.swift in Sources */,
DF4C1AA5205AEDFC00DED50B /* SafeAreaViewController.swift in Sources */,
DFEAF74A20C9648A00E33147 /* WrapContentView.swift in Sources */,
249EFE431E64FAFE00165E39 /* AppDelegate.swift in Sources */,
Expand All @@ -693,9 +715,11 @@
24F246111FA8D4D100B6332E /* CollectionViewExampleViewController.swift in Sources */,
2439CC531E665C6B003326FB /* BetweenViewController.swift in Sources */,
DF4C1AA4205AEDFC00DED50B /* SafeAreaView.swift in Sources */,
C892FA1924A5821E0086A75E /* AutoSizingViewController.swift in Sources */,
241637771F8E4F9100EE703A /* IntroObjectiveCView.m in Sources */,
24CB99A01F290664004EA7FB /* ChoiceSelectorView.swift in Sources */,
249326891EEEEE3D00BCB814 /* Stylesheet.swift in Sources */,
C892FA1D24A584010086A75E /* ContentService.swift in Sources */,
DFD31BA0212EE4F200566CA4 /* TableViewReadableContentView.swift in Sources */,
DF39089A211900480049FD56 /* AnimationsViewController.swift in Sources */,
2439CC521E665C6B003326FB /* BetweenView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
41 changes: 41 additions & 0 deletions Example/PinLayoutSample/UI/Common/ContentService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import UIKit

class ContentService {
static let shared = ContentService()

private init() {}

func fetchText(numberOfParagraph: Int = 1, completionHandler: ((Result<[String], Error>) -> Void)?) {
URLSession.shared.dataTask(with: URL(string: "https://baconipsum.com/api/?type=all-meat&paras=\(numberOfParagraph)&start-with-lorem=1")!) { data, _, error in
guard let data = data, error == nil,
let paragraphs = try? JSONDecoder().decode([String].self, from: data)
else {
DispatchQueue.main.async {
completionHandler?(Result<[String], Error>.failure(error!))
}
return
}

DispatchQueue.main.async {
completionHandler?(Result<[String], Error>.success(paragraphs))
}
}.resume()
}

func fetchImage(width: Int, height: Int, completionHandler: ((Result<UIImage, Error>) -> Void)?) {
URLSession.shared.dataTask(with: URL(string: "https://baconmockup.com/\(width)/\(height)")!) { data, _, error in
guard let data = data, error == nil,
let image = UIImage(data: data)
else {
DispatchQueue.main.async {
completionHandler?(Result<UIImage, Error>.failure(error!))
}
return
}

DispatchQueue.main.async {
completionHandler?(Result<UIImage, Error>.success(image))
}
}.resume()
}
}
31 changes: 31 additions & 0 deletions Example/PinLayoutSample/UI/Common/ProxyWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Foundation

@propertyWrapper
public struct Proxy<Value, EnclosingSelf> {
private let keyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>

public init(_ keyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>) {
self.keyPath = keyPath
}

public var wrappedValue: Value {
get { fatalError() }
set { fatalError() }
}

public static subscript(
_enclosingInstance observed: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
) -> Value {
get {
let storageValue = observed[keyPath: storageKeyPath]
let value = observed[keyPath: storageValue.keyPath]
return value
}
set {
let storageValue = observed[keyPath: storageKeyPath]
observed[keyPath: storageValue.keyPath] = newValue
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import UIKit

final class AutoSizingContainerView: UIView {
private let imageView = UIImageView()
private let firstTextLabel = UILabel()
private let secondTextLabel = UILabel()

private let margin: CGFloat = 10

@Proxy(\AutoSizingContainerView.imageView.image)
var image: UIImage? {
didSet {
setNeedsLayout()
}
}

@Proxy(\AutoSizingContainerView.firstTextLabel.text)
var firstText: String? {
didSet {
setNeedsLayout()
}
}

@Proxy(\AutoSizingContainerView.secondTextLabel.text)
var secondText: String? {
didSet {
setNeedsLayout()
}
}

init() {
super.init(frame: CGRect.zero)
configureView()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func configureView() {
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
addSubview(imageView)

firstTextLabel.numberOfLines = 0
firstTextLabel.backgroundColor = UIColor.orange.withAlphaComponent(0.3)
addSubview(firstTextLabel)

secondTextLabel.numberOfLines = 0
secondTextLabel.backgroundColor = UIColor.green.withAlphaComponent(0.3)
addSubview(secondTextLabel)
}

override func layoutSubviews() {
super.layoutSubviews()
performLayout()
}

private func performLayout() {
imageView.pin.top().horizontally().sizeToFit(.width).margin(margin)
firstTextLabel.pin.below(of: imageView).horizontally().sizeToFit(.width).margin(margin)
secondTextLabel.pin.below(of: firstTextLabel).horizontally().sizeToFit(.width).margin(margin)
}

override func sizeThatFits(_ size: CGSize) -> CGSize {
autoSizeThatFits(size, layoutClosure: performLayout)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import UIKit
import PinLayout

final class AutoSizingView: UIView {
private let scrollView = UIScrollView()
private let containerView = AutoSizingContainerView()

private let margin: CGFloat = 30

init() {
super.init(frame: CGRect.zero)
configureView()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func configureView() {
backgroundColor = .white

scrollView.alwaysBounceVertical = true
addSubview(scrollView)

containerView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.3)
scrollView.addSubview(containerView)
}

func updateImage(_ image: UIImage?) {
containerView.image = image
setNeedsLayout()
}

func updateTexts(firstText: String?, secondText: String?) {
containerView.firstText = firstText
containerView.secondText = secondText
setNeedsLayout()
}

override func layoutSubviews() {
super.layoutSubviews()
performLayout()
didPerformLayout()
}

private func performLayout() {
scrollView.pin.all()
containerView.pin.top(margin).horizontally(margin).sizeToFit(.width)
}

private func didPerformLayout() {
scrollView.contentSize = CGSize(width: bounds.width, height: containerView.frame.maxY + margin)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import UIKit

class AutoSizingViewController: UIViewController {
private var mainView: AutoSizingView {
return self.view as! AutoSizingView
}

override func loadView() {
self.view = AutoSizingView()
}

override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
randomizeContent()
}

private func configureNavigationBar() {
navigationItem.title = "AutoSizing"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Randomize", style: .plain, target: self, action: #selector(randomizeContent))
}

@objc
private func randomizeContent() {
ContentService.shared.fetchText(numberOfParagraph: 2) { [weak self] (result) in
guard let strongSelf = self, case let .success(paragraphs) = result else { return }
strongSelf.mainView.updateTexts(firstText: paragraphs[0], secondText: paragraphs[1])
}

ContentService.shared.fetchImage(width: Int.random(in: 200..<500), height: Int.random(in: 200..<500)) { [weak self] (result) in
guard let strongSelf = self, case let .success(image) = result else { return }
strongSelf.mainView.updateImage(image)
}
}
}
4 changes: 4 additions & 0 deletions Example/PinLayoutSample/UI/Menu/MenuViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum PageType: Int {
case between
case form
case wrapContent
case autoSizing
case tableViewWithReadable
case introRTL
case introObjC
Expand All @@ -50,6 +51,7 @@ enum PageType: Int {
case .between: return "Between Example"
case .form: return "Form Example"
case .wrapContent: return "wrapContent Example"
case .autoSizing: return "Auto Sizing"
case .tableViewWithReadable: return "UITableView using readableMargins"
case .introRTL: return "Right-to-left Language Support"
case .introObjC: return "Objective-C PinLayout Example"
Expand Down Expand Up @@ -84,6 +86,8 @@ enum PageType: Int {
return FormViewController(pageType: self)
case .wrapContent:
return WrapContentViewController(pageType: self)
case .autoSizing:
return AutoSizingViewController()
case .tableViewWithReadable:
return TableViewReadableContentViewController(pageType: self)
case .introRTL:
Expand Down
1 change: 1 addition & 0 deletions PinLayout.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Pod::Spec.new do |spec|
spec.author = { "Luc Dion" => "luc_dion@yahoo.com" }
spec.source = { :git => "https://github.com/layoutBox/PinLayout.git", :tag => "#{spec.version}" }
spec.source_files = "Sources/**/*.swift"
spec.swift_version = '5.0'

spec.ios.deployment_target = '8.0'
spec.ios.frameworks = 'Foundation', 'CoreGraphics', 'UIKit'
Expand Down
Loading

0 comments on commit 21e53ef

Please sign in to comment.