Skip to content

Commit

Permalink
Campaign: Klimabrølet (#467)
Browse files Browse the repository at this point in the history
* Add needed assets for Klima brølet

* Initial implementation of the Klima Brølet view

This is a new campaign at the style of Earth Hour.

I initially copied and modified the earth hour views and adapted them to
this current use case, without the custom header animation.

One of the extensions from this view in relation to the Earth Hour one
is the support for dynamic type and scrollable content.

An extension to this view is to make it a more reusable component as a
campaign card component. The main reason I didn't do it right now is
because time-to-ship is fairly limited, together with I am not sure how
to solve the potential injection of a view as the scrollable content of
the campaign card.

* Show shadow on top of action buttons when there is more content

* Expose delegate methods for KlimabroletView

* Replace demo view with demo view controller

In order to be able to use the nested navigation controller and the table view
controller

* Use a KVO observation to set the initial shadow of the action view

Initially I was trying to check if the content was bigger than the
scroll view in `layoutSubviews`, but the content view didn't have a
resolved frame _for some reeason_.

So in order to determine if the action view needed to have an initial
shadow if the user can scroll, I opted for use a `KeyValueObservation`
to detect when the scrollView's content size changes.

* Add screenshot test for the Klimabrølet view

* Fix naming of local variable

* Rename `newClose` to `cross`

* Add `weak` to delegates

* Mark methods private

* Remove unnecessary text

* Rename item in asset catalog to be `cross` instead of `newCross`

Otherwise the naming will get out of sync

* Use a configure method to set the KlimabroletView data

* Use configure methods to set the content of Klimabrolet subviews

In the case of `KlimabroletContentView` I decided to pass the full view
model for the amount of properties, also i felt it's not necessary to
create a custom view model for this nested view as it's intended to work
as a nested view of `KlimabroletView`.

* Favor setting constraint constants over using margins guide

Creating constraints towards the `layoutMarginGuide` reads better, but
the behavior is different for iOS 10 (I am not sure exactly why, it's
not mentioned in the docs). The result in iOS 10 would be like to set a
constraint to the actual border of the view, not considering its
margins.

Then to ensure the layout works as intended for all of our supported iOS
versions, I set the constants instead.

* Ensure close button is visible for iOS 10

For some reason, the button was resolving auto layout with dimensions
equal to 0, then the close button wasn't visibile on iOS 10

* Improve demo data used

* Do not cut the text on EarthHourTagView

The image has a fixed heigh of `14`, then with the `.fill` alignment, the text
will be restricted to be 14 points.

* Ensure ReadMore button is big enough to tap

* Allow touches to go immediately to the content of the scroll view

So there is no delay when tapping a button inside the scroll view

* Update Klimabrolet snapshot reference

* Override stack view aligment outside of EarthHourTagView

I do this as I don't want to change the behavior of the `EarthHourTagView` for
its original uses, but as i want to reuse it, i can just allow to override the stack
view alignment in order not to clip the text

* Fix Feedback Demo View reference

It got lost within rebasing with master due to the conflicts in
FinniversKit.xcodeproj

* Hide getters for buttons in actions view

* Redefine how to express the scroll view content constraints

* Adjust demo event url
  • Loading branch information
fespinoza authored Jul 31, 2019
1 parent 7bf9d09 commit 94bf520
Show file tree
Hide file tree
Showing 20 changed files with 721 additions and 9 deletions.
41 changes: 41 additions & 0 deletions Demo/Components/Klimabrolet/KlimabroletData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Copyright © 2019 FINN AS. All rights reserved.
//

import FinniversKit

struct KlimabroletData {
static let campaignURL = URL(string: "https://klimabrolet.no")

struct ViewModel: KlimabroletViewModel {
let title: String = "Bli med og BRØØØL!"
let subtitle: String = "30 August kl. 15.00"
let bodyText: String = "Barn og unge over hele verden har samlet seg i gatene til støtte for miljøet. Ikke la dem stå alene. Bli med og brøl for klimaet!"
let readMoreButtonTitle: String = "Les mer om Klimabrølet"
let acceptButtonTitle: String = "Bli med på Klimabrølet!"
let declineButtonTitle: String = "Nei takk"
}

struct Event {
let name: String
let url: URL? = URL(string: "https://klimabrolet.no/faq")
}

static let events: [Event] = [
Event(name: "Klimabrølet Oslo"),
Event(name: "Klimabrølet Tønsberg"),
Event(name: "Klimabrølet Stavanger"),
Event(name: "Klimabrølet Bergen"),
Event(name: "Klimabrølet Røros"),
Event(name: "Klimabrølet Trondheim"),
Event(name: "Klimabrølet Henningsvær"),
Event(name: "Klimabrølet Svalbard")
]
}

extension KlimabroletData.Event: BasicTableViewCellViewModel {
var title: String { return name }
var subtitle: String? { return nil }
var detailText: String? { return nil }
var hasChevron: Bool { return false }
}
106 changes: 106 additions & 0 deletions Demo/Components/Klimabrolet/KlimabroletDemoViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// Copyright © 2019 FINN AS. All rights reserved.
//

import FinniversKit

class KlimabroletDemoViewController: DemoViewController<UIView> {
private lazy var klimabroletView: KlimabroletView = {
let view = KlimabroletView(withAutoLayout: true)
view.configure(with: KlimabroletData.ViewModel())
view.delegate = self
return view
}()

private lazy var klimabroletViewController: UIViewController = {
let controller = UIViewController()
controller.view.addSubview(klimabroletView)
controller.setNeedsStatusBarAppearanceUpdate()
klimabroletView.fillInSuperview()

return controller
}()

private lazy var eventListViewController: KlimabroletEventsDemoViewController = {
let controller = KlimabroletEventsDemoViewController(style: .plain)
controller.delegate = self
return controller
}()

private lazy var innerNavigationController: UINavigationController = {
let navigation = UINavigationController(rootViewController: klimabroletViewController)
navigation.view.translatesAutoresizingMaskIntoConstraints = false
navigation.setNavigationBarHidden(true, animated: false)
navigation.view.layer.cornerRadius = 20
navigation.view.clipsToBounds = true
navigation.delegate = self

return navigation
}()

override func viewDidLoad() {
super.viewDidLoad()
playgroundView.backgroundColor = .black

addChild(innerNavigationController)
innerNavigationController.didMove(toParent: self)
view.addSubview(innerNavigationController.view)

NSLayoutConstraint.activate([
innerNavigationController.view.widthAnchor.constraint(equalToConstant: 320),
innerNavigationController.view.heightAnchor.constraint(equalToConstant: 536),
innerNavigationController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
innerNavigationController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}

private func close() {
State.lastSelectedIndexPath = nil
dismiss(animated: true, completion: nil)
}
}

extension KlimabroletDemoViewController: KlimabroletViewDelegate {
func klimabroletViewDidSelectReadMore(_ view: KlimabroletView) {
guard let url = KlimabroletData.campaignURL else {
return
}

UIApplication.shared.open(url)
}

func klimabroletViewDidSelectAccept(_ view: KlimabroletView) {
innerNavigationController.pushViewController(eventListViewController, animated: true)
}

func klimabroletViewDidSelectDecline(_ view: KlimabroletView) {
close()
}

func klimabroletViewDidSelectClose(_ view: KlimabroletView) {
close()
}
}

extension KlimabroletDemoViewController: KlimabroletEventsDemoViewControllerDelegate {
func eventList(_ eventListViewController: KlimabroletEventsDemoViewController, didSelect event: KlimabroletData.Event) {
guard let url = event.url else {
return
}

UIApplication.shared.open(url)
}

func eventListDidSelectClose(_ eventListViewController: KlimabroletEventsDemoViewController) {
close()
}
}

extension KlimabroletDemoViewController: UINavigationControllerDelegate {
func navigationController(
_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool
) {
let needsNavigationBarHidden = viewController == klimabroletViewController
innerNavigationController.setNavigationBarHidden(needsNavigationBarHidden, animated: true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Copyright © 2019 FINN AS. All rights reserved.
//

import FinniversKit

protocol KlimabroletEventsDemoViewControllerDelegate: AnyObject {
func eventList(_ eventListViewController: KlimabroletEventsDemoViewController, didSelect event: KlimabroletData.Event)
func eventListDidSelectClose(_ eventListViewController: KlimabroletEventsDemoViewController)
}

class KlimabroletEventsDemoViewController: UITableViewController {
let events: [KlimabroletData.Event] = KlimabroletData.events

weak var delegate: KlimabroletEventsDemoViewControllerDelegate?

private lazy var closeButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: .cross).withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = .stone
button.addTarget(self, action: #selector(handleCloseButtonTap), for: .touchUpInside)
return button
}()

override init(style: UITableView.Style) {
super.init(style: style)
}

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

override func viewDidLoad() {
title = "Velg arrangement"
setupNavigationBar()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: closeButton)
NSLayoutConstraint.activate([
closeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 12),
closeButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 12),
])
tableView.register(BasicTableViewCell.self)
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeue(BasicTableViewCell.self, for: indexPath)
let model = events[indexPath.row]
cell.configure(with: model)
cell.selectionStyle = .default
return cell
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 48
}

override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .milk
return view
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let event = events[indexPath.row]
delegate?.eventList(self, didSelect: event)
}

private func setupNavigationBar() {
guard let navigationBar = navigationController?.navigationBar else {
return
}

navigationBar.isTranslucent = false
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
}

@objc private func handleCloseButtonTap() {
delegate?.eventListDidSelectClose(self)
}
}
3 changes: 3 additions & 0 deletions Demo/Demo/Demo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public enum ComponentViews: String, CaseIterable {
case feedbackView
case happinessRating
case earthHour
case klimabrolet
case stepIndicator
case nativeAdvert
case callout
Expand Down Expand Up @@ -129,6 +130,8 @@ public enum ComponentViews: String, CaseIterable {
return DemoViewController<HappinessRatingDemoView>(withDismissButton: true)
case .earthHour:
return DemoViewController<EarthHourDemoView>()
case .klimabrolet:
return KlimabroletDemoViewController(usingDoubleTapToDismiss: false)
case .stepIndicator:
return DemoViewController<StepIndicatorDemoView>(withDismissButton: true)
case .nativeAdvert:
Expand Down
Loading

0 comments on commit 94bf520

Please sign in to comment.