-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b6dccc4
commit f414a66
Showing
18 changed files
with
1,625 additions
and
0 deletions.
There are no files selected for viewing
540 changes: 540 additions & 0 deletions
540
0039-witness-oriented-library-design/PointFreeFramework.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
...-library-design/PointFreeFramework.xcodeproj/project.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
...gn/PointFreeFramework.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
200 changes: 200 additions & 0 deletions
200
0039-witness-oriented-library-design/PointFreeFramework/EpisodesViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import Foundation | ||
import UIKit | ||
import Overture | ||
|
||
final class SubscribeCalloutCell: UITableViewCell { | ||
private let bodyLabel = UILabel() | ||
private let buttonsStackView = UIStackView() | ||
private let cardView = UIView() | ||
private let loginButton = UIButton() | ||
private let orLabel = UILabel() | ||
private let rootStackView = UIStackView() | ||
private let subscribeButton = UIButton() | ||
private let titleLabel = UILabel() | ||
|
||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
|
||
self.selectionStyle = .none | ||
self.contentView.layoutMargins = .init(top: .pf_grid(6), left: .pf_grid(6), bottom: .pf_grid(6), right: .pf_grid(6)) | ||
|
||
self.titleLabel.text = "Subscribe to Point-Free" | ||
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .title3) | ||
|
||
self.bodyLabel.text = "👋 Hey there! See anything you like? You may be interested in subscribing so that you get access to these episodes and all future ones." | ||
self.bodyLabel.numberOfLines = 0 | ||
self.bodyLabel.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.subheadline) | ||
|
||
self.cardView.backgroundColor = UIColor(white: 0.96, alpha: 1.0) | ||
with(self.cardView, generousMargins) | ||
self.cardView.translatesAutoresizingMaskIntoConstraints = false | ||
self.contentView.addSubview(self.cardView) | ||
|
||
self.rootStackView.alignment = .leading | ||
with(self.rootStackView, baseStackViewStyle) | ||
self.rootStackView.addArrangedSubview(self.titleLabel) | ||
self.rootStackView.addArrangedSubview(self.bodyLabel) | ||
self.rootStackView.addArrangedSubview(self.buttonsStackView) | ||
self.contentView.addSubview(self.rootStackView) | ||
|
||
self.orLabel.text = "or" | ||
self.orLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) | ||
|
||
self.subscribeButton.setTitle("See subscription options", for: .normal) | ||
with(self.subscribeButton, primaryButtonStyle) | ||
|
||
self.loginButton.setTitle("Login", for: .normal) | ||
with(self.loginButton, secondaryTextButtonStyle) | ||
|
||
self.buttonsStackView.spacing = .pf_grid(2) | ||
self.buttonsStackView.alignment = .firstBaseline | ||
self.buttonsStackView.addArrangedSubview(self.subscribeButton) | ||
self.buttonsStackView.addArrangedSubview(self.orLabel) | ||
self.buttonsStackView.addArrangedSubview(self.loginButton) | ||
|
||
NSLayoutConstraint.activate([ | ||
self.rootStackView.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor), | ||
self.rootStackView.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor), | ||
self.rootStackView.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor), | ||
self.rootStackView.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor), | ||
|
||
self.cardView.leadingAnchor.constraint(equalTo: self.rootStackView.leadingAnchor), | ||
self.cardView.topAnchor.constraint(equalTo: self.rootStackView.topAnchor), | ||
self.cardView.trailingAnchor.constraint(equalTo: self.rootStackView.trailingAnchor), | ||
self.cardView.bottomAnchor.constraint(equalTo: self.rootStackView.bottomAnchor), | ||
]) | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
} | ||
|
||
final class EpisodeCell: UITableViewCell { | ||
private let blurbLabel = UILabel() | ||
private let contentStackView = UIStackView() | ||
private let posterImageView = UIImageView() | ||
private let rootStackView = UIStackView() | ||
private let sequenceAndDateLabel = UILabel() | ||
private let subscriberOnlyLabel = UILabel() | ||
private lazy var subscriberOnlyLabelWrapper = with( | ||
wrapView( | ||
padding: UIEdgeInsets( | ||
top: .pf_grid(1), | ||
left: .pf_grid(2), | ||
bottom: .pf_grid(1), | ||
right: .pf_grid(2) | ||
) | ||
)(self.subscriberOnlyLabel), | ||
concat( | ||
autoLayoutStyle, | ||
baseRoundedStyle, | ||
mut(\UIView.backgroundColor, UIColor(white: 0, alpha: 0.3)) | ||
) | ||
) | ||
|
||
private let titleLabel = UILabel() | ||
private let watchNowButton = UIButton() | ||
|
||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
|
||
self.blurbLabel.numberOfLines = 0 | ||
self.blurbLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) | ||
|
||
with(self.contentStackView, concat( | ||
baseStackViewStyle, | ||
mut(\.layoutMargins.bottom, .pf_grid(8)) | ||
)) | ||
self.contentStackView.alignment = .leading | ||
self.contentStackView.addArrangedSubview(self.sequenceAndDateLabel) | ||
self.contentStackView.addArrangedSubview(self.titleLabel) | ||
self.contentStackView.addArrangedSubview(self.blurbLabel) | ||
self.contentStackView.addArrangedSubview(self.watchNowButton) | ||
|
||
with(self.rootStackView, concat( | ||
autoLayoutStyle, | ||
verticalStackView | ||
)) | ||
self.rootStackView.addArrangedSubview(self.posterImageView) | ||
self.rootStackView.addArrangedSubview(self.contentStackView) | ||
|
||
with(self.sequenceAndDateLabel, smallCapsLabelStyle) | ||
|
||
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .title2) | ||
|
||
self.watchNowButton.setTitle("Watch episode →", for: .normal) | ||
with(self.watchNowButton, primaryTextButtonStyle) | ||
|
||
self.subscriberOnlyLabel.text = "Subscriber Only" | ||
with(self.subscriberOnlyLabel, concat( | ||
smallCapsLabelStyle, | ||
mut(\.textColor, .white) | ||
)) | ||
|
||
self.contentView.addSubview(self.rootStackView) | ||
// self.contentView.addSubview(self.subscriberOnlyLabelWrapper) | ||
|
||
NSLayoutConstraint.activate([ | ||
self.rootStackView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor), | ||
self.rootStackView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor), | ||
self.rootStackView.topAnchor.constraint(equalTo: self.contentView.topAnchor), | ||
self.rootStackView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), | ||
|
||
self.posterImageView.widthAnchor.constraint(equalTo: self.posterImageView.heightAnchor, multiplier: 16/9), | ||
|
||
// self.subscriberOnlyLabelWrapper.topAnchor.constraint(equalTo: self.posterImageView.topAnchor, constant: .pf_grid(3)), | ||
// self.subscriberOnlyLabelWrapper.trailingAnchor.constraint(equalTo: self.posterImageView.trailingAnchor, constant: -.pf_grid(6)), | ||
]) | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
func configure(with episode: Episode) { | ||
self.posterImageView.backgroundColor = episode.color | ||
self.titleLabel.text = episode.title | ||
self.blurbLabel.text = episode.blurb | ||
let formattedDate = episodeDateFormatter.string(from: episode.publishedAt) | ||
self.sequenceAndDateLabel.text = "#\(episode.sequence) • \(formattedDate)" | ||
self.subscriberOnlyLabelWrapper.isHidden = !episode.subscriberOnly | ||
|
||
URLSession.shared.dataTask(with: URL(string: episode.posterImageUrl)!) { data, _, _ in | ||
DispatchQueue.main.async { self.posterImageView.image = data.flatMap(UIImage.init(data:)) } | ||
}.resume() | ||
} | ||
} | ||
|
||
public final class EpisodeListViewController: UITableViewController { | ||
let episodes: [Episode] | ||
|
||
init<Episodes: Collection>(episodes: Episodes) where Episodes.Element == Episode { | ||
self.episodes = Array(episodes) | ||
super.init(nibName: nil, bundle: nil) | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override public func viewDidLoad() { | ||
super.viewDidLoad() | ||
self.tableView.estimatedRowHeight = 400 | ||
self.tableView.rowHeight = UITableView.automaticDimension | ||
} | ||
|
||
override public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | ||
if indexPath.row == 0 { | ||
return SubscribeCalloutCell(style: .default, reuseIdentifier: nil) | ||
} | ||
|
||
let cell = EpisodeCell(style: .default, reuseIdentifier: nil) | ||
cell.configure(with: self.episodes[indexPath.row - 1]) | ||
return cell | ||
} | ||
|
||
override public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | ||
return self.episodes.count + 1 | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
0039-witness-oriented-library-design/PointFreeFramework/Info.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?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>CFBundleDevelopmentRegion</key> | ||
<string>$(DEVELOPMENT_LANGUAGE)</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>FMWK</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleVersion</key> | ||
<string>$(CURRENT_PROJECT_VERSION)</string> | ||
</dict> | ||
</plist> |
70 changes: 70 additions & 0 deletions
70
0039-witness-oriented-library-design/PointFreeFramework/Model.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import Foundation | ||
import UIKit | ||
|
||
public struct Episode { | ||
public let blurb: String | ||
public let color: UIColor | ||
public let posterImageUrl: String | ||
public let publishedAt: Date | ||
public let sequence: Int | ||
public let subscriberOnly: Bool | ||
public let title: String | ||
} | ||
|
||
public let episodes: [Episode] = [ | ||
// .init( | ||
// blurb: """ | ||
//What does the Swift type system have to do with algebra? A lot! We’ll begin to explore this correspondence and see how it can help us create type-safe data structures that can catch runtime errors at compile time. | ||
//""", | ||
// color: .pf_yellow, | ||
// posterImageUrl: "https://d1hf1soyumxcgv.cloudfront.net/0004-adt/0004-poster.jpg", | ||
// publishedAt: Date(timeIntervalSince1970: 1_519_045_951), | ||
// sequence: 4, | ||
// subscriberOnly: true, | ||
// title: "Algebraic Data Types" | ||
// ), | ||
.init( | ||
blurb: """ | ||
We bring tools from previous episodes down to earth and apply them to an everyday task: UIKit styling. Plain functions unlock worlds of composability and reusability in styling of UI components. Have we finally solved the styling problem? | ||
""", | ||
color: .pf_purple, | ||
posterImageUrl: "https://d1hf1soyumxcgv.cloudfront.net/0003-styling-with-functions/0003-poster.jpg", | ||
publishedAt: Date(timeIntervalSince1970: 1_519_045_951), | ||
sequence: 3, | ||
subscriberOnly: false, | ||
title: "UIKit Styling with Functions" | ||
), | ||
.init( | ||
blurb: """ | ||
Side effects: can’t live with ’em; can’t write a program without ’em. Let’s explore a few kinds of side effects we encounter every day, why they make code difficult to reason about and test, and how we can control them without losing composition. | ||
""", | ||
color: .pf_blue, | ||
posterImageUrl: "https://d1hf1soyumxcgv.cloudfront.net/0002-side-effects/0002-poster.jpg", | ||
publishedAt: Date(timeIntervalSince1970: 1_517_811_069), | ||
sequence: 2, | ||
subscriberOnly: false, | ||
title: "Side Effects" | ||
), | ||
.init( | ||
blurb: """ | ||
Our first episode is all about functions! We talk a bit about what makes functions special, contrasting them with the way we usually write code, and have some exploratory discussions about operators and composition. | ||
""", | ||
color: .pf_green, | ||
posterImageUrl: "https://d1hf1soyumxcgv.cloudfront.net/0001-functions/0001-poster.jpg", | ||
publishedAt: Date(timeIntervalSince1970: 1_517_206_269), | ||
sequence: 1, | ||
subscriberOnly: false, | ||
title: "Functions" | ||
), | ||
.init( | ||
blurb: """ | ||
Point-Free is here, bringing you videos covering functional programming concepts using the Swift language. Take a moment to hear from the hosts about what to expect from this new series. | ||
""", | ||
color: .pf_yellow, | ||
posterImageUrl: "https://d1hf1soyumxcgv.cloudfront.net/0000-introduction/0000-poster.jpg", | ||
publishedAt: Date(timeIntervalSince1970: 1_517_206_269), | ||
sequence: 0, | ||
subscriberOnly: false, | ||
title: "We launched!" | ||
), | ||
] |
3 changes: 3 additions & 0 deletions
3
0039-witness-oriented-library-design/PointFreeFramework/PointFreeFramework.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#import <UIKit/UIKit.h> | ||
FOUNDATION_EXPORT double PointFreeFrameworkVersionNumber; | ||
FOUNDATION_EXPORT const unsigned char PointFreeFrameworkVersionString[]; |
82 changes: 82 additions & 0 deletions
82
0039-witness-oriented-library-design/PointFreeFramework/StyleGuide.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import Foundation | ||
import UIKit | ||
import Overture | ||
|
||
extension CGFloat { | ||
static func pf_grid(_ n: Int) -> CGFloat { | ||
return CGFloat(n) * 4 | ||
} | ||
} | ||
|
||
let generousMargins = | ||
mut(\UIView.layoutMargins, .init(top: .pf_grid(6), left: .pf_grid(6), bottom: .pf_grid(6), right: .pf_grid(6))) | ||
|
||
let autoLayoutStyle = mut(\UIView.translatesAutoresizingMaskIntoConstraints, false) | ||
|
||
let verticalStackView = mut(\UIStackView.axis, .vertical) | ||
|
||
let baseStackViewStyle = concat( | ||
generousMargins, | ||
mut(\UIStackView.spacing, .pf_grid(3)), | ||
verticalStackView, | ||
mut(\.isLayoutMarginsRelativeArrangement, true), | ||
autoLayoutStyle | ||
) | ||
|
||
let bolded: (inout UIFont) -> Void = { $0 = $0.bolded } | ||
|
||
let baseTextButtonStyle = concat( | ||
mut(\UIButton.titleLabel!.font, UIFont.preferredFont(forTextStyle: .subheadline)), | ||
mver(\UIButton.titleLabel!.font!, bolded) | ||
) | ||
|
||
extension UIButton { | ||
var normalTitleColor: UIColor? { | ||
get { return self.titleColor(for: .normal) } | ||
set { self.setTitleColor(newValue, for: .normal) } | ||
} | ||
} | ||
|
||
let secondaryTextButtonStyle = concat( | ||
baseTextButtonStyle, | ||
mut(\.normalTitleColor, .black) | ||
) | ||
|
||
let primaryTextButtonStyle = concat( | ||
baseTextButtonStyle, | ||
mut(\.normalTitleColor, .pf_purple) | ||
) | ||
|
||
let baseButtonStyle = concat( | ||
baseTextButtonStyle, | ||
mut(\.contentEdgeInsets, .init(top: .pf_grid(2), left: .pf_grid(4), bottom: .pf_grid(2), right: .pf_grid(4))) | ||
) | ||
|
||
func roundedStyle(cornerRadius: CGFloat) -> (UIView) -> Void { | ||
return concat( | ||
mut(\.layer.cornerRadius, cornerRadius), | ||
mut(\.layer.masksToBounds, true) | ||
) | ||
} | ||
|
||
let baseRoundedStyle = roundedStyle(cornerRadius: 6) | ||
|
||
let baseFilledButtonStyle = concat( | ||
baseButtonStyle, | ||
baseRoundedStyle | ||
) | ||
|
||
extension UIButton { | ||
var normalBackgroundImage: UIImage? { | ||
get { return self.backgroundImage(for: .normal) } | ||
set { self.setBackgroundImage(newValue, for: .normal) } | ||
} | ||
} | ||
|
||
let primaryButtonStyle = concat( | ||
baseFilledButtonStyle, | ||
mut(\.normalBackgroundImage, .from(color: .pf_purple)), | ||
mut(\.normalTitleColor, .white) | ||
) | ||
|
||
let smallCapsLabelStyle = mut(\UILabel.font, UIFont.preferredFont(forTextStyle: .caption1).smallCaps) |
Oops, something went wrong.