Skip to content

Commit 21e53ef

Browse files
authored
Merge pull request #216 from antoinelamy/feature/AutoSizing
Add automatic sizeThatFits computation for views
2 parents 930593a + b702f19 commit 21e53ef

19 files changed

+456
-72
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
language: objective-c
2-
osx_image: xcode11.2
2+
osx_image: xcode11.5
33

44
cache:
55
- bundler

Example/PinLayoutSample.xcodeproj/project.pbxproj

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@
4141
24F246141FA8D57100B6332E /* UIImageView+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F246131FA8D57100B6332E /* UIImageView+Download.swift */; };
4242
24F75B5B1EE5644E008DB567 /* IntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F75B591EE5644E008DB567 /* IntroView.swift */; };
4343
24F75B5C1EE5644E008DB567 /* IntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F75B5A1EE5644E008DB567 /* IntroViewController.swift */; };
44-
DE6C3D736B571B80E207DF6A /* Pods_PinLayoutSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAD69688AA2A3F0994F3074E /* Pods_PinLayoutSample.framework */; };
44+
C892FA1924A5821E0086A75E /* AutoSizingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1824A5821E0086A75E /* AutoSizingViewController.swift */; };
45+
C892FA1B24A5822B0086A75E /* AutoSizingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1A24A5822B0086A75E /* AutoSizingView.swift */; };
46+
C892FA1D24A584010086A75E /* ContentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1C24A584010086A75E /* ContentService.swift */; };
47+
C892FA1F24A597FA0086A75E /* AutoSizingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA1E24A597FA0086A75E /* AutoSizingContainerView.swift */; };
48+
C892FA2124A598170086A75E /* ProxyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C892FA2024A598170086A75E /* ProxyWrapper.swift */; };
4549
DF390898211900320049FD56 /* AnimationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF390897211900320049FD56 /* AnimationsView.swift */; };
4650
DF39089A211900480049FD56 /* AnimationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF390899211900480049FD56 /* AnimationsViewController.swift */; };
4751
DF4C1AA4205AEDFC00DED50B /* SafeAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4C1AA0205AEDFC00DED50B /* SafeAreaView.swift */; };
@@ -148,8 +152,12 @@
148152
24F75B591EE5644E008DB567 /* IntroView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroView.swift; sourceTree = "<group>"; };
149153
24F75B5A1EE5644E008DB567 /* IntroViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroViewController.swift; sourceTree = "<group>"; };
150154
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>"; };
151-
AAD69688AA2A3F0994F3074E /* Pods_PinLayoutSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PinLayoutSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
152155
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>"; };
156+
C892FA1824A5821E0086A75E /* AutoSizingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingViewController.swift; sourceTree = "<group>"; };
157+
C892FA1A24A5822B0086A75E /* AutoSizingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingView.swift; sourceTree = "<group>"; };
158+
C892FA1C24A584010086A75E /* ContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentService.swift; sourceTree = "<group>"; };
159+
C892FA1E24A597FA0086A75E /* AutoSizingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSizingContainerView.swift; sourceTree = "<group>"; };
160+
C892FA2024A598170086A75E /* ProxyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyWrapper.swift; sourceTree = "<group>"; };
153161
DF390897211900320049FD56 /* AnimationsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimationsView.swift; path = PinLayoutSample/UI/Examples/Animations/AnimationsView.swift; sourceTree = SOURCE_ROOT; };
154162
DF390899211900480049FD56 /* AnimationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationsViewController.swift; sourceTree = "<group>"; };
155163
DF4C1AA0205AEDFC00DED50B /* SafeAreaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeAreaView.swift; sourceTree = "<group>"; };
@@ -173,7 +181,6 @@
173181
isa = PBXFrameworksBuildPhase;
174182
buildActionMask = 2147483647;
175183
files = (
176-
DE6C3D736B571B80E207DF6A /* Pods_PinLayoutSample.framework in Frameworks */,
177184
);
178185
runOnlyForDeploymentPostprocessing = 0;
179186
};
@@ -183,7 +190,6 @@
183190
160FB83905049FCEDD18DC8A /* Frameworks */ = {
184191
isa = PBXGroup;
185192
children = (
186-
AAD69688AA2A3F0994F3074E /* Pods_PinLayoutSample.framework */,
187193
246812FC1F8D013500462E53 /* NotificationCenter.framework */,
188194
);
189195
name = Frameworks;
@@ -240,6 +246,7 @@
240246
DFD31B9D212EE4D600566CA4 /* TableViewReadableContent */,
241247
24D18D181F3DECD6008129EF /* IntroRTL */,
242248
2416376F1F8E4BC200EE703A /* IntroObjectiveC */,
249+
C892FA1124A582050086A75E /* AutoSizing */,
243250
24CD1E8D1F8E4B0A00C3A54D /* PinLayoutSample-Bridging-Header.h */,
244251
);
245252
path = Examples;
@@ -255,6 +262,8 @@
255262
24F246131FA8D57100B6332E /* UIImageView+Download.swift */,
256263
DFD27840211B1A700056BD93 /* UINavigationController+Orientation.swift */,
257264
DFD27847211B1D090056BD93 /* UITabBarController+Orientation.swift */,
265+
C892FA1C24A584010086A75E /* ContentService.swift */,
266+
C892FA2024A598170086A75E /* ProxyWrapper.swift */,
258267
);
259268
path = Common;
260269
sourceTree = "<group>";
@@ -399,6 +408,16 @@
399408
path = Intro;
400409
sourceTree = "<group>";
401410
};
411+
C892FA1124A582050086A75E /* AutoSizing */ = {
412+
isa = PBXGroup;
413+
children = (
414+
C892FA1824A5821E0086A75E /* AutoSizingViewController.swift */,
415+
C892FA1A24A5822B0086A75E /* AutoSizingView.swift */,
416+
C892FA1E24A597FA0086A75E /* AutoSizingContainerView.swift */,
417+
);
418+
path = AutoSizing;
419+
sourceTree = "<group>";
420+
};
402421
DF3908912118FFF20049FD56 /* Animations */ = {
403422
isa = PBXGroup;
404423
children = (
@@ -661,10 +680,12 @@
661680
24F75B5C1EE5644E008DB567 /* IntroViewController.swift in Sources */,
662681
241637741F8E4BC200EE703A /* IntroObjectiveCViewController.m in Sources */,
663682
DF390898211900320049FD56 /* AnimationsView.swift in Sources */,
683+
C892FA2124A598170086A75E /* ProxyWrapper.swift in Sources */,
664684
2439CC541E665C6B003326FB /* RelativeView.swift in Sources */,
665685
2439CC551E665C6B003326FB /* RelativeViewController.swift in Sources */,
666686
247157941F87BD680003424F /* UIEdgeInsets+PinLayout.swift in Sources */,
667687
24D18D1E1F3DED0D008129EF /* IntroRTLViewController.swift in Sources */,
688+
C892FA1F24A597FA0086A75E /* AutoSizingContainerView.swift in Sources */,
668689
DFEAF74C20C9649F00E33147 /* WrapContentViewController.swift in Sources */,
669690
24F246121FA8D4D100B6332E /* HouseCell.swift in Sources */,
670691
DF4C1AAE205AF78A00DED50B /* SafeAreaAndMarginsViewController.swift in Sources */,
@@ -681,6 +702,7 @@
681702
24CB999C1F29059B004EA7FB /* AdjustToContainerView.swift in Sources */,
682703
2497CFED1EF40B9100DFD13B /* FormView.swift in Sources */,
683704
24F75B5B1EE5644E008DB567 /* IntroView.swift in Sources */,
705+
C892FA1B24A5822B0086A75E /* AutoSizingView.swift in Sources */,
684706
DF4C1AA5205AEDFC00DED50B /* SafeAreaViewController.swift in Sources */,
685707
DFEAF74A20C9648A00E33147 /* WrapContentView.swift in Sources */,
686708
249EFE431E64FAFE00165E39 /* AppDelegate.swift in Sources */,
@@ -693,9 +715,11 @@
693715
24F246111FA8D4D100B6332E /* CollectionViewExampleViewController.swift in Sources */,
694716
2439CC531E665C6B003326FB /* BetweenViewController.swift in Sources */,
695717
DF4C1AA4205AEDFC00DED50B /* SafeAreaView.swift in Sources */,
718+
C892FA1924A5821E0086A75E /* AutoSizingViewController.swift in Sources */,
696719
241637771F8E4F9100EE703A /* IntroObjectiveCView.m in Sources */,
697720
24CB99A01F290664004EA7FB /* ChoiceSelectorView.swift in Sources */,
698721
249326891EEEEE3D00BCB814 /* Stylesheet.swift in Sources */,
722+
C892FA1D24A584010086A75E /* ContentService.swift in Sources */,
699723
DFD31BA0212EE4F200566CA4 /* TableViewReadableContentView.swift in Sources */,
700724
DF39089A211900480049FD56 /* AnimationsViewController.swift in Sources */,
701725
2439CC521E665C6B003326FB /* BetweenView.swift in Sources */,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import UIKit
2+
3+
class ContentService {
4+
static let shared = ContentService()
5+
6+
private init() {}
7+
8+
func fetchText(numberOfParagraph: Int = 1, completionHandler: ((Result<[String], Error>) -> Void)?) {
9+
URLSession.shared.dataTask(with: URL(string: "https://baconipsum.com/api/?type=all-meat&paras=\(numberOfParagraph)&start-with-lorem=1")!) { data, _, error in
10+
guard let data = data, error == nil,
11+
let paragraphs = try? JSONDecoder().decode([String].self, from: data)
12+
else {
13+
DispatchQueue.main.async {
14+
completionHandler?(Result<[String], Error>.failure(error!))
15+
}
16+
return
17+
}
18+
19+
DispatchQueue.main.async {
20+
completionHandler?(Result<[String], Error>.success(paragraphs))
21+
}
22+
}.resume()
23+
}
24+
25+
func fetchImage(width: Int, height: Int, completionHandler: ((Result<UIImage, Error>) -> Void)?) {
26+
URLSession.shared.dataTask(with: URL(string: "https://baconmockup.com/\(width)/\(height)")!) { data, _, error in
27+
guard let data = data, error == nil,
28+
let image = UIImage(data: data)
29+
else {
30+
DispatchQueue.main.async {
31+
completionHandler?(Result<UIImage, Error>.failure(error!))
32+
}
33+
return
34+
}
35+
36+
DispatchQueue.main.async {
37+
completionHandler?(Result<UIImage, Error>.success(image))
38+
}
39+
}.resume()
40+
}
41+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
3+
@propertyWrapper
4+
public struct Proxy<Value, EnclosingSelf> {
5+
private let keyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>
6+
7+
public init(_ keyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>) {
8+
self.keyPath = keyPath
9+
}
10+
11+
public var wrappedValue: Value {
12+
get { fatalError() }
13+
set { fatalError() }
14+
}
15+
16+
public static subscript(
17+
_enclosingInstance observed: EnclosingSelf,
18+
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
19+
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
20+
) -> Value {
21+
get {
22+
let storageValue = observed[keyPath: storageKeyPath]
23+
let value = observed[keyPath: storageValue.keyPath]
24+
return value
25+
}
26+
set {
27+
let storageValue = observed[keyPath: storageKeyPath]
28+
observed[keyPath: storageValue.keyPath] = newValue
29+
}
30+
}
31+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import UIKit
2+
3+
final class AutoSizingContainerView: UIView {
4+
private let imageView = UIImageView()
5+
private let firstTextLabel = UILabel()
6+
private let secondTextLabel = UILabel()
7+
8+
private let margin: CGFloat = 10
9+
10+
@Proxy(\AutoSizingContainerView.imageView.image)
11+
var image: UIImage? {
12+
didSet {
13+
setNeedsLayout()
14+
}
15+
}
16+
17+
@Proxy(\AutoSizingContainerView.firstTextLabel.text)
18+
var firstText: String? {
19+
didSet {
20+
setNeedsLayout()
21+
}
22+
}
23+
24+
@Proxy(\AutoSizingContainerView.secondTextLabel.text)
25+
var secondText: String? {
26+
didSet {
27+
setNeedsLayout()
28+
}
29+
}
30+
31+
init() {
32+
super.init(frame: CGRect.zero)
33+
configureView()
34+
}
35+
36+
required init?(coder: NSCoder) {
37+
fatalError("init(coder:) has not been implemented")
38+
}
39+
40+
private func configureView() {
41+
imageView.clipsToBounds = true
42+
imageView.contentMode = .scaleAspectFill
43+
addSubview(imageView)
44+
45+
firstTextLabel.numberOfLines = 0
46+
firstTextLabel.backgroundColor = UIColor.orange.withAlphaComponent(0.3)
47+
addSubview(firstTextLabel)
48+
49+
secondTextLabel.numberOfLines = 0
50+
secondTextLabel.backgroundColor = UIColor.green.withAlphaComponent(0.3)
51+
addSubview(secondTextLabel)
52+
}
53+
54+
override func layoutSubviews() {
55+
super.layoutSubviews()
56+
performLayout()
57+
}
58+
59+
private func performLayout() {
60+
imageView.pin.top().horizontally().sizeToFit(.width).margin(margin)
61+
firstTextLabel.pin.below(of: imageView).horizontally().sizeToFit(.width).margin(margin)
62+
secondTextLabel.pin.below(of: firstTextLabel).horizontally().sizeToFit(.width).margin(margin)
63+
}
64+
65+
override func sizeThatFits(_ size: CGSize) -> CGSize {
66+
autoSizeThatFits(size, layoutClosure: performLayout)
67+
}
68+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import UIKit
2+
import PinLayout
3+
4+
final class AutoSizingView: UIView {
5+
private let scrollView = UIScrollView()
6+
private let containerView = AutoSizingContainerView()
7+
8+
private let margin: CGFloat = 30
9+
10+
init() {
11+
super.init(frame: CGRect.zero)
12+
configureView()
13+
}
14+
15+
required init?(coder: NSCoder) {
16+
fatalError("init(coder:) has not been implemented")
17+
}
18+
19+
private func configureView() {
20+
backgroundColor = .white
21+
22+
scrollView.alwaysBounceVertical = true
23+
addSubview(scrollView)
24+
25+
containerView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.3)
26+
scrollView.addSubview(containerView)
27+
}
28+
29+
func updateImage(_ image: UIImage?) {
30+
containerView.image = image
31+
setNeedsLayout()
32+
}
33+
34+
func updateTexts(firstText: String?, secondText: String?) {
35+
containerView.firstText = firstText
36+
containerView.secondText = secondText
37+
setNeedsLayout()
38+
}
39+
40+
override func layoutSubviews() {
41+
super.layoutSubviews()
42+
performLayout()
43+
didPerformLayout()
44+
}
45+
46+
private func performLayout() {
47+
scrollView.pin.all()
48+
containerView.pin.top(margin).horizontally(margin).sizeToFit(.width)
49+
}
50+
51+
private func didPerformLayout() {
52+
scrollView.contentSize = CGSize(width: bounds.width, height: containerView.frame.maxY + margin)
53+
}
54+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import UIKit
2+
3+
class AutoSizingViewController: UIViewController {
4+
private var mainView: AutoSizingView {
5+
return self.view as! AutoSizingView
6+
}
7+
8+
override func loadView() {
9+
self.view = AutoSizingView()
10+
}
11+
12+
override func viewDidLoad() {
13+
super.viewDidLoad()
14+
configureNavigationBar()
15+
}
16+
17+
override func viewWillAppear(_ animated: Bool) {
18+
super.viewWillAppear(animated)
19+
randomizeContent()
20+
}
21+
22+
private func configureNavigationBar() {
23+
navigationItem.title = "AutoSizing"
24+
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Randomize", style: .plain, target: self, action: #selector(randomizeContent))
25+
}
26+
27+
@objc
28+
private func randomizeContent() {
29+
ContentService.shared.fetchText(numberOfParagraph: 2) { [weak self] (result) in
30+
guard let strongSelf = self, case let .success(paragraphs) = result else { return }
31+
strongSelf.mainView.updateTexts(firstText: paragraphs[0], secondText: paragraphs[1])
32+
}
33+
34+
ContentService.shared.fetchImage(width: Int.random(in: 200..<500), height: Int.random(in: 200..<500)) { [weak self] (result) in
35+
guard let strongSelf = self, case let .success(image) = result else { return }
36+
strongSelf.mainView.updateImage(image)
37+
}
38+
}
39+
}

Example/PinLayoutSample/UI/Menu/MenuViewController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum PageType: Int {
3131
case between
3232
case form
3333
case wrapContent
34+
case autoSizing
3435
case tableViewWithReadable
3536
case introRTL
3637
case introObjC
@@ -50,6 +51,7 @@ enum PageType: Int {
5051
case .between: return "Between Example"
5152
case .form: return "Form Example"
5253
case .wrapContent: return "wrapContent Example"
54+
case .autoSizing: return "Auto Sizing"
5355
case .tableViewWithReadable: return "UITableView using readableMargins"
5456
case .introRTL: return "Right-to-left Language Support"
5557
case .introObjC: return "Objective-C PinLayout Example"
@@ -84,6 +86,8 @@ enum PageType: Int {
8486
return FormViewController(pageType: self)
8587
case .wrapContent:
8688
return WrapContentViewController(pageType: self)
89+
case .autoSizing:
90+
return AutoSizingViewController()
8791
case .tableViewWithReadable:
8892
return TableViewReadableContentViewController(pageType: self)
8993
case .introRTL:

PinLayout.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Pod::Spec.new do |spec|
1616
spec.author = { "Luc Dion" => "[email protected]" }
1717
spec.source = { :git => "https://github.com/layoutBox/PinLayout.git", :tag => "#{spec.version}" }
1818
spec.source_files = "Sources/**/*.swift"
19+
spec.swift_version = '5.0'
1920

2021
spec.ios.deployment_target = '8.0'
2122
spec.ios.frameworks = 'Foundation', 'CoreGraphics', 'UIKit'

0 commit comments

Comments
 (0)