Skip to content
Snippets Groups Projects
Commit 4213a99b authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Implement animated scroll tab alternating

parent a1892ab2
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !47. Comments created here will be created in the context of that merge request.
Showing
with 369 additions and 81 deletions
import UIKit
import Theme
import Shared
import Combine
import DependencyInjection
public final class SearchContainerController: UIViewController {
......@@ -8,8 +9,16 @@ public final class SearchContainerController: UIViewController {
lazy private var screenView = SearchContainerView()
private var cancellables = Set<AnyCancellable>()
private let qrController = SearchQRController()
private let emailController = SearchEmailController()
private let phoneController = SearchPhoneController()
private let usernameController = SearchUsernameController()
public override func loadView() {
view = screenView
screenView.scrollView.delegate = self
embedControllers()
}
public override func viewWillAppear(_ animated: Bool) {
......@@ -24,6 +33,7 @@ public final class SearchContainerController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
setupBindings()
}
private func setupNavigationBar() {
......@@ -42,7 +52,107 @@ public final class SearchContainerController: UIViewController {
)
}
private func setupBindings() {
screenView.segmentedControl
.actionPublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
let page = CGFloat($0.rawValue)
let point: CGPoint = CGPoint(x: screenView.frame.width * page, y: 0.0)
screenView.scrollView.setContentOffset(point, animated: true)
}.store(in: &cancellables)
}
@objc private func didTapBack() {
navigationController?.popViewController(animated: true)
}
private func embedControllers() {
addChild(qrController)
addChild(emailController)
addChild(phoneController)
addChild(usernameController)
screenView.scrollView.addSubview(qrController.view)
screenView.scrollView.addSubview(emailController.view)
screenView.scrollView.addSubview(phoneController.view)
screenView.scrollView.addSubview(usernameController.view)
usernameController.view.snp.makeConstraints {
$0.top.equalTo(screenView.segmentedControl.snp.bottom)
$0.width.equalTo(screenView)
$0.bottom.equalTo(screenView)
$0.left.equalToSuperview()
$0.right.equalTo(emailController.view.snp.left)
}
emailController.view.snp.makeConstraints {
$0.top.equalTo(screenView.segmentedControl.snp.bottom)
$0.width.equalTo(screenView)
$0.bottom.equalTo(screenView)
$0.right.equalTo(phoneController.view.snp.left)
}
phoneController.view.snp.makeConstraints {
$0.top.equalTo(screenView.segmentedControl.snp.bottom)
$0.width.equalTo(screenView)
$0.bottom.equalTo(screenView)
$0.right.equalTo(qrController.view.snp.left)
}
qrController.view.snp.makeConstraints {
$0.top.equalTo(screenView.segmentedControl.snp.bottom)
$0.width.equalTo(screenView)
$0.bottom.equalTo(screenView)
}
qrController.didMove(toParent: self)
emailController.didMove(toParent: self)
phoneController.didMove(toParent: self)
usernameController.didMove(toParent: self)
}
}
extension SearchContainerController: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageOffset = scrollView.contentOffset.x / view.frame.width
scrollSegmentedControlTrack(using: pageOffset)
updateSegmentedControlButtonsColor(using: pageOffset)
}
private func scrollSegmentedControlTrack(using pageOffset: CGFloat) {
let amountOfTabs = 4.0
let tabWidth = screenView.bounds.width / amountOfTabs
if let leftConstraint = screenView.segmentedControl.leftConstraint {
leftConstraint.update(offset: pageOffset * tabWidth)
}
}
private func updateSegmentedControlButtonsColor(using pageOffset: CGFloat) {
let qrRate = highlightRateFor(page: 3, offset: pageOffset)
let emailRate = highlightRateFor(page: 1, offset: pageOffset)
let phoneRate = highlightRateFor(page: 2, offset: pageOffset)
let usernameRate = highlightRateFor(page: 0, offset: pageOffset)
screenView.segmentedControl.qrCodeButton.updateHighlighting(rate: qrRate)
screenView.segmentedControl.emailButton.updateHighlighting(rate: emailRate)
screenView.segmentedControl.phoneButton.updateHighlighting(rate: phoneRate)
screenView.segmentedControl.usernameButton.updateHighlighting(rate: usernameRate)
}
private func highlightRateFor(page: CGFloat, offset: CGFloat) -> CGFloat {
let lowerBound = page - 1
let upperBound = page + 1
if offset > lowerBound && offset < upperBound {
if (offset - lowerBound) > 1 {
return 1 - (offset - page)
} else {
return offset - lowerBound
}
} else {
return 0
}
}
}
import UIKit
final class SearchEmailController: UIViewController {
lazy private var screenView = SearchEmailView()
override func loadView() {
view = screenView
}
}
import UIKit
final class SearchPhoneController: UIViewController {
lazy private var screenView = SearchPhoneView()
override func loadView() {
view = screenView
}
}
import UIKit
final class SearchQRController: UIViewController {
lazy private var screenView = SearchQRView()
override func loadView() {
view = screenView
}
}
import UIKit
final class SearchUsernameController: UIViewController {
lazy private var screenView = SearchUsernameView()
override func loadView() {
view = screenView
}
}
import UIKit
import Shared
import InputField
final class SearchEmailView: UIView {
let inputField = InputField()
init() {
super.init(frame: .zero)
inputField.setup(
style: .regular,
title: "Email",
placeholder: "Email"
)
addSubview(inputField)
inputField.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(15)
$0.right.equalToSuperview().offset(-15)
$0.bottom.lessThanOrEqualToSuperview()
}
}
required init?(coder: NSCoder) { nil }
}
import UIKit
import Shared
import InputField
final class SearchPhoneView: UIView {
let inputField = InputField()
init() {
super.init(frame: .zero)
inputField.setup(
style: .regular,
title: "Phone",
placeholder: "Phone"
)
addSubview(inputField)
inputField.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(15)
$0.right.equalToSuperview().offset(-15)
$0.bottom.lessThanOrEqualToSuperview()
}
}
required init?(coder: NSCoder) { nil }
}
import UIKit
import Shared
import InputField
final class SearchQRView: UIView {
let inputField = InputField()
init() {
super.init(frame: .zero)
inputField.setup(
style: .regular,
title: "QR",
placeholder: "QR"
)
addSubview(inputField)
inputField.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(15)
$0.right.equalToSuperview().offset(-15)
$0.bottom.lessThanOrEqualToSuperview()
}
}
required init?(coder: NSCoder) { nil }
}
import UIKit
import Shared
final class SearchUsernamePlaceholderView: UIView {
let titleLabel = UILabel()
init() {
super.init(frame: .zero)
titleLabel.text = "[SearchUsernamePlaceholderView]"
addSubview(titleLabel)
titleLabel.snp.makeConstraints {
$0.center.equalToSuperview()
}
}
required init?(coder: NSCoder) { nil }
}
import UIKit
import Shared
import InputField
final class SearchUsernameView: UIView {
let inputField = InputField()
let placeholderView = SearchUsernamePlaceholderView()
init() {
super.init(frame: .zero)
inputField.setup(
style: .regular,
title: "Username",
placeholder: "Username"
)
addSubview(inputField)
addSubview(placeholderView)
inputField.snp.makeConstraints {
$0.top.equalToSuperview().offset(15)
$0.left.equalToSuperview().offset(15)
$0.right.equalToSuperview().offset(-15)
}
placeholderView.snp.makeConstraints {
$0.top.equalTo(inputField.snp.bottom)
$0.left.equalToSuperview()
$0.right.equalToSuperview()
$0.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) { nil }
}
......@@ -12,16 +12,19 @@ final class SearchContainerView: UIView {
addSubview(segmentedControl)
addSubview(scrollView)
scrollView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
segmentedControl.snp.makeConstraints {
$0.top.equalTo(safeAreaLayoutGuide).offset(10)
$0.left.equalToSuperview()
$0.right.equalToSuperview()
$0.height.equalTo(60)
}
scrollView.snp.makeConstraints {
$0.top.equalTo(segmentedControl.snp.bottom)
$0.left.equalToSuperview()
$0.right.equalToSuperview()
$0.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) { nil }
......
......@@ -2,8 +2,10 @@ import UIKit
import Shared
final class SearchSegmentedButton: UIControl {
let titleLabel = UILabel()
let imageView = UIImageView()
private let titleLabel = UILabel()
private let imageView = UIImageView()
private let highlightColor = Asset.brandPrimary.color
private let discreteColor = Asset.neutralDisabled.color
init() {
super.init(frame: .zero)
......@@ -30,12 +32,25 @@ final class SearchSegmentedButton: UIControl {
required init?(coder: NSCoder) { nil }
func setup(title: String, icon: UIImage) {
titleLabel.text = title
imageView.image = icon
func setup(
title: String,
icon: UIImage,
iconColor: UIColor = Asset.neutralDisabled.color,
titleColor: UIColor = Asset.neutralDisabled.color
) {
self.imageView.image = icon
self.titleLabel.text = title
self.imageView.tintColor = iconColor
self.titleLabel.textColor = titleColor
}
func update(color: UIColor) {
func updateHighlighting(rate: CGFloat) {
let color = UIColor.fade(
from: discreteColor,
to: highlightColor,
pcent: rate
)
imageView.tintColor = color
titleLabel.textColor = color
}
......
import UIKit
import Shared
import SnapKit
import Combine
final class SearchSegmentedControl: UIView {
enum Item: Int {
case username = 0
case email
case phone
case qr
}
private let trackView = UIView()
private let stackView = UIStackView()
private var leftConstraint: Constraint?
private let trackIndicatorView = UIView()
private(set) var leftConstraint: Constraint?
private(set) var usernameButton = SearchSegmentedButton()
private(set) var emailButton = SearchSegmentedButton()
private(set) var phoneButton = SearchSegmentedButton()
private(set) var qrCodeButton = SearchSegmentedButton()
var actionPublisher: AnyPublisher<Item, Never> {
actionSubject.eraseToAnyPublisher()
}
private var cancellables = Set<AnyCancellable>()
private let actionSubject = PassthroughSubject<Item, Never>()
init() {
super.init(frame: .zero)
trackView.backgroundColor = Asset.neutralLine.color
trackIndicatorView.backgroundColor = Asset.brandPrimary.color
qrCodeButton.titleLabel.text = Localized.Ud.Tab.qr
emailButton.titleLabel.text = Localized.Ud.Tab.email
phoneButton.titleLabel.text = Localized.Ud.Tab.phone
usernameButton.titleLabel.text = Localized.Ud.Tab.username
usernameButton.titleLabel.textColor = Asset.brandPrimary.color
emailButton.titleLabel.textColor = Asset.neutralDisabled.color
phoneButton.titleLabel.textColor = Asset.neutralDisabled.color
qrCodeButton.titleLabel.textColor = Asset.neutralDisabled.color
usernameButton.imageView.tintColor = Asset.brandPrimary.color
emailButton.imageView.tintColor = Asset.neutralDisabled.color
phoneButton.imageView.tintColor = Asset.neutralDisabled.color
qrCodeButton.imageView.tintColor = Asset.neutralDisabled.color
usernameButton.setup(
title: Localized.Ud.Tab.username,
icon: Asset.searchTabUsername.image,
iconColor: Asset.brandPrimary.color,
titleColor: Asset.brandPrimary.color
)
qrCodeButton.imageView.image = Asset.searchTabQr.image
emailButton.imageView.image = Asset.searchTabEmail.image
phoneButton.imageView.image = Asset.searchTabPhone.image
usernameButton.imageView.image = Asset.searchTabUsername.image
qrCodeButton.setup(title: Localized.Ud.Tab.qr, icon: Asset.searchTabQr.image)
emailButton.setup(title: Localized.Ud.Tab.email, icon: Asset.searchTabEmail.image)
phoneButton.setup(title: Localized.Ud.Tab.phone, icon: Asset.searchTabPhone.image)
stackView.distribution = .fillEqually
stackView.addArrangedSubview(usernameButton)
stackView.addArrangedSubview(emailButton)
stackView.addArrangedSubview(phoneButton)
stackView.addArrangedSubview(qrCodeButton)
stackView.distribution = .fillEqually
stackView.backgroundColor = Asset.neutralWhite.color
addSubview(stackView)
addSubview(trackView)
trackView.addSubview(trackIndicatorView)
setupBindings()
setupConstraints()
}
required init?(coder: NSCoder) { nil }
private func setupBindings() {
usernameButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in actionSubject.send(.username) }
.store(in: &cancellables)
emailButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in actionSubject.send(.email) }
.store(in: &cancellables)
phoneButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in actionSubject.send(.phone) }
.store(in: &cancellables)
qrCodeButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in actionSubject.send(.qr) }
.store(in: &cancellables)
}
private func setupConstraints() {
stackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
......@@ -66,55 +101,4 @@ final class SearchSegmentedControl: UIView {
$0.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) { nil }
func updateSwipePercentage(_ percentageScrolled: CGFloat) {
let amountOfTabs = 4.0
let tabWidth = bounds.width / amountOfTabs
let leftOffset = percentageScrolled * tabWidth
leftConstraint?.update(offset: leftOffset)
let usernamePercentage = percentageScrolled > 1 ? 1 : percentageScrolled
let phonePercentage = percentageScrolled <= 1 ? 0 : percentageScrolled - 1
let emailPercentage = percentageScrolled > 1 ? 1 - (percentageScrolled-1) : percentageScrolled
let qrPercentage = percentageScrolled > 1 ? 1 - (percentageScrolled-1) : percentageScrolled
let usernameColor = UIColor.fade(
from: Asset.brandPrimary.color,
to: Asset.neutralDisabled.color,
pcent: usernamePercentage
)
let emailColor = UIColor.fade(
from: Asset.neutralDisabled.color,
to: Asset.brandPrimary.color,
pcent: emailPercentage
)
let phoneColor = UIColor.fade(
from: Asset.neutralDisabled.color,
to: Asset.brandPrimary.color,
pcent: phonePercentage
)
let qrColor = UIColor.fade(
from: Asset.brandPrimary.color,
to: Asset.neutralDisabled.color,
pcent: qrPercentage
)
usernameButton.imageView.tintColor = usernameColor
usernameButton.titleLabel.textColor = usernameColor
emailButton.imageView.tintColor = emailColor
emailButton.titleLabel.textColor = emailColor
phoneButton.imageView.tintColor = phoneColor
phoneButton.titleLabel.textColor = phoneColor
qrCodeButton.imageView.tintColor = qrColor
qrCodeButton.titleLabel.textColor = qrColor
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment