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
Showing
with 369 additions and 81 deletions
import UIKit import UIKit
import Theme import Theme
import Shared import Shared
import Combine
import DependencyInjection import DependencyInjection
public final class SearchContainerController: UIViewController { public final class SearchContainerController: UIViewController {
...@@ -8,8 +9,16 @@ public final class SearchContainerController: UIViewController { ...@@ -8,8 +9,16 @@ public final class SearchContainerController: UIViewController {
lazy private var screenView = SearchContainerView() 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() { public override func loadView() {
view = screenView view = screenView
screenView.scrollView.delegate = self
embedControllers()
} }
public override func viewWillAppear(_ animated: Bool) { public override func viewWillAppear(_ animated: Bool) {
...@@ -24,6 +33,7 @@ public final class SearchContainerController: UIViewController { ...@@ -24,6 +33,7 @@ public final class SearchContainerController: UIViewController {
public override func viewDidLoad() { public override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setupNavigationBar() setupNavigationBar()
setupBindings()
} }
private func setupNavigationBar() { private func setupNavigationBar() {
...@@ -42,7 +52,107 @@ public final class SearchContainerController: UIViewController { ...@@ -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() { @objc private func didTapBack() {
navigationController?.popViewController(animated: true) 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 { ...@@ -12,16 +12,19 @@ final class SearchContainerView: UIView {
addSubview(segmentedControl) addSubview(segmentedControl)
addSubview(scrollView) addSubview(scrollView)
scrollView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
segmentedControl.snp.makeConstraints { segmentedControl.snp.makeConstraints {
$0.top.equalTo(safeAreaLayoutGuide).offset(10) $0.top.equalTo(safeAreaLayoutGuide).offset(10)
$0.left.equalToSuperview() $0.left.equalToSuperview()
$0.right.equalToSuperview() $0.right.equalToSuperview()
$0.height.equalTo(60) $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 } required init?(coder: NSCoder) { nil }
......
...@@ -2,8 +2,10 @@ import UIKit ...@@ -2,8 +2,10 @@ import UIKit
import Shared import Shared
final class SearchSegmentedButton: UIControl { final class SearchSegmentedButton: UIControl {
let titleLabel = UILabel() private let titleLabel = UILabel()
let imageView = UIImageView() private let imageView = UIImageView()
private let highlightColor = Asset.brandPrimary.color
private let discreteColor = Asset.neutralDisabled.color
init() { init() {
super.init(frame: .zero) super.init(frame: .zero)
...@@ -30,12 +32,25 @@ final class SearchSegmentedButton: UIControl { ...@@ -30,12 +32,25 @@ final class SearchSegmentedButton: UIControl {
required init?(coder: NSCoder) { nil } required init?(coder: NSCoder) { nil }
func setup(title: String, icon: UIImage) { func setup(
titleLabel.text = title title: String,
imageView.image = icon 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 imageView.tintColor = color
titleLabel.textColor = color titleLabel.textColor = color
} }
......
import UIKit import UIKit
import Shared import Shared
import SnapKit import SnapKit
import Combine
final class SearchSegmentedControl: UIView { final class SearchSegmentedControl: UIView {
enum Item: Int {
case username = 0
case email
case phone
case qr
}
private let trackView = UIView() private let trackView = UIView()
private let stackView = UIStackView() private let stackView = UIStackView()
private var leftConstraint: Constraint?
private let trackIndicatorView = UIView() private let trackIndicatorView = UIView()
private(set) var leftConstraint: Constraint?
private(set) var usernameButton = SearchSegmentedButton() private(set) var usernameButton = SearchSegmentedButton()
private(set) var emailButton = SearchSegmentedButton() private(set) var emailButton = SearchSegmentedButton()
private(set) var phoneButton = SearchSegmentedButton() private(set) var phoneButton = SearchSegmentedButton()
private(set) var qrCodeButton = 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() { init() {
super.init(frame: .zero) super.init(frame: .zero)
trackView.backgroundColor = Asset.neutralLine.color trackView.backgroundColor = Asset.neutralLine.color
trackIndicatorView.backgroundColor = Asset.brandPrimary.color trackIndicatorView.backgroundColor = Asset.brandPrimary.color
qrCodeButton.titleLabel.text = Localized.Ud.Tab.qr usernameButton.setup(
emailButton.titleLabel.text = Localized.Ud.Tab.email title: Localized.Ud.Tab.username,
phoneButton.titleLabel.text = Localized.Ud.Tab.phone icon: Asset.searchTabUsername.image,
usernameButton.titleLabel.text = Localized.Ud.Tab.username iconColor: Asset.brandPrimary.color,
titleColor: Asset.brandPrimary.color
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
qrCodeButton.imageView.image = Asset.searchTabQr.image qrCodeButton.setup(title: Localized.Ud.Tab.qr, icon: Asset.searchTabQr.image)
emailButton.imageView.image = Asset.searchTabEmail.image emailButton.setup(title: Localized.Ud.Tab.email, icon: Asset.searchTabEmail.image)
phoneButton.imageView.image = Asset.searchTabPhone.image phoneButton.setup(title: Localized.Ud.Tab.phone, icon: Asset.searchTabPhone.image)
usernameButton.imageView.image = Asset.searchTabUsername.image
stackView.distribution = .fillEqually
stackView.addArrangedSubview(usernameButton) stackView.addArrangedSubview(usernameButton)
stackView.addArrangedSubview(emailButton) stackView.addArrangedSubview(emailButton)
stackView.addArrangedSubview(phoneButton) stackView.addArrangedSubview(phoneButton)
stackView.addArrangedSubview(qrCodeButton) stackView.addArrangedSubview(qrCodeButton)
stackView.distribution = .fillEqually
stackView.backgroundColor = Asset.neutralWhite.color stackView.backgroundColor = Asset.neutralWhite.color
addSubview(stackView) addSubview(stackView)
addSubview(trackView) addSubview(trackView)
trackView.addSubview(trackIndicatorView) 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 { stackView.snp.makeConstraints {
$0.edges.equalToSuperview() $0.edges.equalToSuperview()
} }
...@@ -66,55 +101,4 @@ final class SearchSegmentedControl: UIView { ...@@ -66,55 +101,4 @@ final class SearchSegmentedControl: UIView {
$0.bottom.equalToSuperview() $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