diff --git a/Scoop/Controller/Onboarding/AboutYouViewController.swift b/Scoop/Controller/Onboarding/AboutYouViewController.swift index 224e905..ecdb54a 100644 --- a/Scoop/Controller/Onboarding/AboutYouViewController.swift +++ b/Scoop/Controller/Onboarding/AboutYouViewController.swift @@ -30,18 +30,24 @@ class AboutYouViewController: OnboardingViewController { view.backgroundColor = .white setupTitle(name: "About you") - nextAction = UIAction { _ in + nextAction = UIAction { [self] _ in guard let navCtrl = self.navigationController else { return } + + [nameTextField, pronounsTextField, hometownTextField, yearTextField].forEach { textField in + if (textField.textField.text ?? "").isEmpty { + textField.displayError() + } + } guard let name = self.nameTextField.textField.text, !name.isEmpty, let pronouns = self.pronounsTextField.textField.text, !pronouns.isEmpty, let hometown = self.hometownTextField.textField.text, !hometown.isEmpty, let year = self.yearTextField.textField.text, !year.isEmpty else { - self.presentErrorAlert(title: "Error", message: "Please complete all fields.") - return - } + return + } + NetworkManager.shared.currentUser.pronouns = pronouns NetworkManager.shared.currentUser.grade = year let hometownTrimmed = hometown.trimmingCharacters(in: .whitespacesAndNewlines) @@ -65,9 +71,6 @@ class AboutYouViewController: OnboardingViewController { var stackViewMultiplier = 0.20 let leadingTrailingInset = 32 let screenSize = UIScreen.main.bounds - let textFieldBorderWidth = 1.0 - let textFieldCornerRadius = 4.0 - let textFieldFont = UIFont(name: "SFPro", size: 16) let textFieldHeight = 56 if screenSize.height < 2000 { @@ -150,25 +153,23 @@ class AboutYouViewController: OnboardingViewController { extension AboutYouViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - return !(textField == yearTextField || textField == pronounsTextField) + return !(textField == yearTextField.textField || textField == pronounsTextField.textField) } func textFieldDidBeginEditing(_ textField: UITextField) { - if let onboardingTextField = textField as? OnboardingTextField { - if let associatedView = onboardingTextField.associatedView as? LabeledTextField { - associatedView.labeledTextField(isSelected: true) - associatedView.hidesLabel(isHidden: false) - } + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + associatedView.hidesLabel(isHidden: false) } } func textFieldDidEndEditing(_ textField: UITextField) { - if let onboardingTextField = textField as? OnboardingTextField { - if let associatedView = onboardingTextField.associatedView as? LabeledTextField { - associatedView.labeledTextField(isSelected: false) - if textField.text?.isEmpty ?? true { - associatedView.hidesLabel(isHidden: true) - } + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: false) + if textField.text?.isEmpty ?? true { + associatedView.hidesLabel(isHidden: true) } } @@ -183,6 +184,13 @@ extension AboutYouViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.endEditing(true) } + + func textFieldDidChangeSelection(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + } + } } diff --git a/Scoop/Controller/Onboarding/FavoritesViewController.swift b/Scoop/Controller/Onboarding/FavoritesViewController.swift index e554370..33dc29e 100644 --- a/Scoop/Controller/Onboarding/FavoritesViewController.swift +++ b/Scoop/Controller/Onboarding/FavoritesViewController.swift @@ -23,24 +23,28 @@ class FavoritesViewController: OnboardingViewController { view.backgroundColor = .white setupTitle(name: "Favorites") - nextAction = UIAction { _ in - guard let navCtrl = self.navigationController else { return } - - guard let snack = self.snackTextField.textField.text, !snack.isEmpty, - let song = self.songTextField.textField.text, !song.isEmpty, - let stop = self.stopTextField.textField.text, !stop.isEmpty else { - self.presentErrorAlert(title: "Error", message: "Please complete all fields.") - - return - } + nextAction = UIAction { [self] _ in + guard let navCtrl = navigationController else { return } + + [snackTextField, songTextField, stopTextField].forEach { textField in + if (textField.textField.text ?? "").isEmpty { + textField.displayError() + } + } + + guard let snack = snackTextField.textField.text, !snack.isEmpty, + let song = songTextField.textField.text, !song.isEmpty, + let stop = stopTextField.textField.text, !stop.isEmpty else { + return + } let snackTrimmed = snack.trimmingCharacters(in: .whitespacesAndNewlines) let songTrimmed = song.trimmingCharacters(in: .whitespacesAndNewlines) let stopTrimmed = stop.trimmingCharacters(in: .whitespacesAndNewlines) - self.addPrompt(name: "Snack", placeholder: "Chips", answer: snackTrimmed) - self.addPrompt(name: "Song", placeholder: "Favorite Song", answer: songTrimmed) - self.addPrompt(name: "Stop", placeholder: "Gates Hall", answer: stopTrimmed) - self.delegate?.didTapNext(navCtrl, nextViewController: nil) + addPrompt(name: "Snack", placeholder: "Chips", answer: snackTrimmed) + addPrompt(name: "Song", placeholder: "Favorite Song", answer: songTrimmed) + addPrompt(name: "Stop", placeholder: "Gates Hall", answer: stopTrimmed) + delegate?.didTapNext(navCtrl, nextViewController: nil) } setupTitleLines() @@ -53,9 +57,6 @@ class FavoritesViewController: OnboardingViewController { // MARK: - Setup View Functions private func setupStackView() { - let textFieldBorderWidth = 1.0 - let textFieldCornerRadius = 4.0 - let textFieldFont = UIFont(name: "SFPro", size: 16) let leadingTrailingInset = 32 let textFieldHeight = 56 @@ -105,21 +106,19 @@ class FavoritesViewController: OnboardingViewController { extension FavoritesViewController: UITextFieldDelegate { func textFieldDidBeginEditing(_ textField: UITextField) { - if let onboardingTextField = textField as? OnboardingTextField { - if let associatedView = onboardingTextField.associatedView as? LabeledTextField { - associatedView.labeledTextField(isSelected: true) - associatedView.hidesLabel(isHidden: false) - } + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + associatedView.hidesLabel(isHidden: false) } } func textFieldDidEndEditing(_ textField: UITextField) { - if let onboardingTextField = textField as? OnboardingTextField { - if let associatedView = onboardingTextField.associatedView as? LabeledTextField { - associatedView.labeledTextField(isSelected: false) - if textField.text?.isEmpty ?? true { - associatedView.hidesLabel(isHidden: true) - } + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: false) + if textField.text?.isEmpty ?? true { + associatedView.hidesLabel(isHidden: true) } } @@ -130,6 +129,13 @@ extension FavoritesViewController: UITextFieldDelegate { setNextButtonColor(disabled: !textFieldsComplete(texts: responses)) } + + func textFieldDidChangeSelection(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + } + } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.endEditing(true) diff --git a/Scoop/Controller/Onboarding/PreferredContactViewController.swift b/Scoop/Controller/Onboarding/PreferredContactViewController.swift index 3593e79..38ce6b1 100644 --- a/Scoop/Controller/Onboarding/PreferredContactViewController.swift +++ b/Scoop/Controller/Onboarding/PreferredContactViewController.swift @@ -46,7 +46,7 @@ class PreferredContactViewController: OnboardingViewController { } guard let phoneNumber = self.numberTextField.textField.text, self.validateNumber(value: phoneNumber) else { - self.presentErrorAlert(title: "Error", message: "Please enter a valid phone number.") + self.numberTextField.displayError() return } @@ -137,7 +137,7 @@ class PreferredContactViewController: OnboardingViewController { numberTextField.isHidden = true numberTextField.delegate = self - numberTextField.setup(title: "Phone", placeholder: "000-000-0000") + numberTextField.setup(title: "Phone", placeholder: "000-000-0000", error: "Please enter a valid phone number") numberTextField.textField.keyboardType = .phonePad view.addSubview(numberTextField) @@ -172,31 +172,32 @@ extension PreferredContactViewController: UITextFieldDelegate { } func textFieldDidChangeSelection(_ textField: UITextField) { - if textField == numberTextField.textField { - self.setNextButtonColor(disabled: !self.validateNumber(value: self.numberTextField.textField.text ?? "")) + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) } + + setNextButtonColor(disabled: !validateNumber(value: textField.text ?? "")) } func textFieldDidBeginEditing(_ textField: UITextField) { - if let onboardingTextField = textField as? OnboardingTextField { - if let associatedView = onboardingTextField.associatedView as? LabeledTextField { - associatedView.labeledTextField(isSelected: true) - associatedView.hidesLabel(isHidden: false) - } + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + associatedView.hidesLabel(isHidden: false) } } func textFieldDidEndEditing(_ textField: UITextField) { - if let onboardingTextField = textField as? OnboardingTextField { - if let associatedView = onboardingTextField.associatedView as? LabeledTextField { - associatedView.labeledTextField(isSelected: false) - if textField.text?.isEmpty ?? true { - associatedView.hidesLabel(isHidden: true) - } + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: false) + if textField.text?.isEmpty ?? true { + associatedView.hidesLabel(isHidden: true) } } } - + func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.endEditing(true) } diff --git a/Scoop/Controller/Ride Posting/InitialPostRideViewController.swift b/Scoop/Controller/Ride Posting/InitialPostRideViewController.swift index 7d5b2f7..1e35e84 100644 --- a/Scoop/Controller/Ride Posting/InitialPostRideViewController.swift +++ b/Scoop/Controller/Ride Posting/InitialPostRideViewController.swift @@ -12,11 +12,9 @@ import UIKit class InitialPostRideViewController: PostRideViewController { // MARK: - Views - - private let arrivalLabel = UILabel() - private let arrivalTextField = ShiftedRightTextField() - private let departureLabel = UILabel() - private let departureTextField = ShiftedRightTextField() + + private let arrivalTextField = LabeledTextField(isShifted: true) + private let departureTextField = LabeledTextField(isShifted: true) private let nextButton = UIButton() private let prevButton = UIButton() private let studentDriverButton = UIButton() @@ -36,32 +34,35 @@ class InitialPostRideViewController: PostRideViewController { // MARK: Using OnboardingViewController's views - nextAction = UIAction { _ in + nextAction = UIAction { [self] _ in guard let navCtrl = self.navigationController else { return } - - guard let arrival = self.arrivalTextField.text, !arrival.isEmpty, - let departure = self.departureTextField.text, !departure.isEmpty, - self.studentDriverButton.isSelected || self.taxiButton.isSelected - else { - self.presentErrorAlert(title: "Error", message: "Please complete all fields.") - return + + [departureTextField, arrivalTextField].forEach { textField in + if (textField.textField.text ?? "").isEmpty { + textField.displayError() + } } - NetworkManager.shared.currentRide.type = self.studentDriverButton.isSelected ? "Student Driver" : "Shared Taxi" - if let arrival = self.arrivalTextField.text, - let departure = self.departureTextField.text, - let arrivalID = self.arrivalLocationID, - let departureID = self.departureLocationID { + guard let arrival = arrivalTextField.textField.text, !arrival.isEmpty, + let departure = departureTextField.textField.text, !departure.isEmpty, + studentDriverButton.isSelected || taxiButton.isSelected + else { return } + + NetworkManager.shared.currentRide.type = studentDriverButton.isSelected ? "Student Driver" : "Shared Taxi" + if let arrival = arrivalTextField.textField.text, + let departure = departureTextField.textField.text, + let arrivalID = arrivalLocationID, + let departureID = departureLocationID { NetworkManager.shared.currentRide.path.endLocationPlaceId = arrivalID NetworkManager.shared.currentRide.path.endLocationName = arrival NetworkManager.shared.currentRide.path.startLocationName = departure NetworkManager.shared.currentRide.path.startLocationPlaceId = departureID } - self.setupBackButton() - self.delegate?.didTapNext(navCtrl, nextViewController: nil) + setupBackButton() + delegate?.didTapNext(navCtrl, nextViewController: nil) } setupNextButton(action: nextAction ?? UIAction(handler: { _ in @@ -73,7 +74,6 @@ class InitialPostRideViewController: PostRideViewController { setupTaxiButton() setupDepartureTextField() setupArrivalTextField() - setupLabels() setupBackButton() backButton.addTarget(self, action: #selector(dismissView), for: .touchUpInside) } @@ -140,23 +140,17 @@ class InitialPostRideViewController: PostRideViewController { private func setupDepartureTextField() { let labelFont = UIFont(name: "Rambla-Regular", size: 16) - let textFieldBorderWidth = 1.0 - let textFieldCornerRadius = 8.0 - let textFieldFont = UIFont(name: "SFPro", size: 16) + let textFieldHeight = 56 let departureLabel = UILabel() departureLabel.text = "Departure" departureLabel.font = labelFont departureLabel.accessibilityLabel = "name" departureLabel.textColor = UIColor.scooped.offBlack - - departureTextField.layer.borderWidth = textFieldBorderWidth - departureTextField.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - departureTextField.layer.cornerRadius = textFieldCornerRadius - departureTextField.font = textFieldFont - departureTextField.textColor = .darkGray - departureTextField.attributedPlaceholder = NSAttributedString(string:"Departure location", attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) - departureTextField.addTarget(self, action: #selector(presentDepartureSearch), for: .touchDown) + + departureTextField.delegate = self + departureTextField.setup(title: "Departure location") + departureTextField.textField.addTarget(self, action: #selector(presentDepartureSearch), for: .touchDown) // Sets up the icon inside the text field. let iconContainer = UIView(frame: CGRect(x: 0, y: 0, width: 25, height: 15)) @@ -165,36 +159,32 @@ class InitialPostRideViewController: PostRideViewController { departureIcon.contentMode = .scaleAspectFit iconContainer.addSubview(departureIcon) - departureTextField.leftView = iconContainer; - departureTextField.leftViewMode = UITextField.ViewMode.always - departureTextField.leftViewMode = .always + departureTextField.textField.leftView = iconContainer; + departureTextField.textField.leftViewMode = UITextField.ViewMode.always + departureTextField.textField.leftViewMode = .always view.addSubview(departureTextField) departureTextField.snp.makeConstraints { make in make.leading.equalTo(titleLabel.snp.leading) - make.top.equalTo(taxiButton.snp.bottom).offset(24) + make.top.equalTo(taxiButton.snp.bottom).offset(36) make.trailing.equalToSuperview().inset(32) + make.height.equalTo(textFieldHeight) } } private func setupArrivalTextField() { let labelFont = UIFont(name: "Rambla-Regular", size: 16) - let textFieldBorderWidth = 1.0 - let textFieldCornerRadius = 8.0 - let textFieldFont = UIFont(name: "SFPro", size: 16) + let textFieldHeight = 56 let arrivalLabel = UILabel() arrivalLabel.text = "Departure" arrivalLabel.font = labelFont arrivalLabel.accessibilityLabel = "name" arrivalLabel.textColor = UIColor.scooped.offBlack - - arrivalTextField.textColor = .darkGray - arrivalTextField.layer.borderWidth = textFieldBorderWidth - arrivalTextField.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - arrivalTextField.layer.cornerRadius = textFieldCornerRadius - arrivalTextField.font = textFieldFont - arrivalTextField.attributedPlaceholder = NSAttributedString(string:"Arrival location", attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) + + arrivalTextField.delegate = self + arrivalTextField.setup(title: "Arrival location") + // Sets up the icon inside the text field. let iconContainer = UIView(frame: CGRect(x: 0, y: 0, width: 25, height: 15)) let arrivalIcon = UIImageView(frame: CGRect(x: 10, y: -2.5, width: 20, height: 20)) @@ -202,45 +192,16 @@ class InitialPostRideViewController: PostRideViewController { arrivalIcon.contentMode = .scaleAspectFit iconContainer.addSubview(arrivalIcon) - arrivalTextField.leftView = iconContainer; - arrivalTextField.leftViewMode = .always - arrivalTextField.addTarget(self, action: #selector(presentArrivalSearch), for: .touchDown) + arrivalTextField.textField.leftView = iconContainer; + arrivalTextField.textField.leftViewMode = .always + arrivalTextField.textField.addTarget(self, action: #selector(presentArrivalSearch), for: .touchDown) view.addSubview(arrivalTextField) arrivalTextField.snp.makeConstraints { make in make.leading.equalTo(titleLabel.snp.leading) make.top.equalTo(departureTextField.snp.bottom).offset(24) make.trailing.equalToSuperview().inset(32) - } - } - - private func setupLabels() { - let labelLeading = 10 - let labelTop = 8 - [departureLabel, arrivalLabel].forEach { label in - label.font = .systemFont(ofSize: 12) - label.textColor = UIColor.scooped.scoopDarkGreen - label.backgroundColor = .white - label.textAlignment = .center - label.isHidden = true - view.addSubview(label) - } - - departureLabel.text = "Departure" - arrivalLabel.text = "Arrival" - - departureLabel.snp.makeConstraints { make in - make.top.equalTo(departureTextField).inset(-labelTop) - make.leading.equalTo(departureTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(65) - } - - arrivalLabel.snp.makeConstraints { make in - make.top.equalTo(arrivalTextField).inset(-labelTop) - make.leading.equalTo(arrivalTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(40) + make.height.equalTo(textFieldHeight) } } @@ -279,7 +240,7 @@ class InitialPostRideViewController: PostRideViewController { private func checkButtonStatus() { var responses: [String] = [] [arrivalTextField, departureTextField].forEach { textField in - responses.append(textField.text ?? "") + responses.append(textField.textField.text ?? "") } setNextButtonColor(disabled: !textFieldsComplete(texts: responses)) @@ -293,18 +254,59 @@ extension InitialPostRideViewController: SearchInitialViewControllerDelegate { func didSelectLocation(viewController: UIViewController, location: GMSPlace) { if viewController is DepartureSearchViewController { - departureTextField.text = location.name + departureTextField.textField.text = location.name departureLocationID = location.placeID - departureLabel.textColor = UIColor.scooped.offBlack - departureLabel.isHidden = false + departureTextField.hidesLabel(isHidden: false) + departureTextField.hideError() } else if viewController is ArrivalSearchViewController { - arrivalTextField.text = location.name + arrivalTextField.textField.text = location.name arrivalLocationID = location.placeID - arrivalLabel.textColor = UIColor.scooped.offBlack - arrivalLabel.isHidden = false + arrivalTextField.hidesLabel(isHidden: false) + arrivalTextField.hideError() } checkButtonStatus() } } + +// MARK: - UITextFieldDelegate + +extension InitialPostRideViewController: UITextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + associatedView.hidesLabel(isHidden: false) + } + } + + func textFieldDidEndEditing(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: false) + if textField.text?.isEmpty ?? true { + associatedView.hidesLabel(isHidden: true) + } + } + + var responses: [String] = [] + [departureTextField, arrivalTextField].forEach { textField in + responses.append(textField.textField.text ?? "") + } + + setNextButtonColor(disabled: !textFieldsComplete(texts: responses)) + } + + func textFieldDidChangeSelection(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + } + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.endEditing(true) + } +} diff --git a/Scoop/Controller/Ride Posting/PostRideTripDetailsViewController.swift b/Scoop/Controller/Ride Posting/PostRideTripDetailsViewController.swift index 42740f2..6d64898 100644 --- a/Scoop/Controller/Ride Posting/PostRideTripDetailsViewController.swift +++ b/Scoop/Controller/Ride Posting/PostRideTripDetailsViewController.swift @@ -26,28 +26,21 @@ class PostRideTripDetailsViewController: PostRideViewController { private let stackView = UIStackView() - private let dateTextField = OnboardingTextField() + private let dateTextField = LabeledTextField() private let datePicker = UIDatePicker() private let calendarIcon = UIImageView() - private let requiredLabel = UILabel() - private let timeTextField = OnboardingTextField() + private let timeTextField = LabeledTextField() private let timePicker = UIDatePicker() private let travelersLabel = UILabel() - private let minTextField = OnboardingTextField() - private let maxTextField = OnboardingTextField() + private let minMaxLabel = UILabel() + private let minTextField = LabeledTextField() + private let maxTextField = LabeledTextField() private let travelersContainerView = UIView() private let detailsExample = UILabel() - private let detailsTextField = UITextView() - - private let dateLabel = UILabel() - private let timeLabel = UILabel() - private let minLabel = UILabel() - private let maxLabel = UILabel() - private let detailsLabel = UILabel() - + private let detailsTextField = LabeledTextView() private var ride: Ride! @@ -68,38 +61,51 @@ class PostRideTripDetailsViewController: PostRideViewController { super.viewDidLoad() view.backgroundColor = .white - nextAction = UIAction { _ in - guard let navCtrl = self.navigationController else { return } + nextAction = UIAction { [self ]_ in + guard let navCtrl = navigationController else { return } + + [dateTextField, timeTextField, minTextField, maxTextField].forEach { textField in + if (textField.textField.text ?? "").isEmpty { + textField.displayError() + } + } - guard let travelerCountLowerText = self.minTextField.text, - let travelerCountUpperText = self.maxTextField.text else { - self.presentErrorAlert(title: "Error", message: "Please complete all fields.") - return - } + guard let travelerCountLowerText = minTextField.textField.text, !travelerCountLowerText.isEmpty, + let travelerCountUpperText = maxTextField.textField.text, !travelerCountUpperText.isEmpty else { return } guard let travelerCountLower = Int(travelerCountLowerText), let travelerCountUpper = Int(travelerCountUpperText), travelerCountLower <= travelerCountUpper, - let date = self.getTravelDate() else { - self.presentErrorAlert(title: "Error", message: "Please enter valid input.") - return - } - + let date = getTravelDate() else { + minTextField.displayError() + maxTextField.displayError() + minTextField.errorLabel.isHidden = true + maxTextField.errorLabel.isHidden = true + minMaxLabel.text = "The minimum must be less than the maximum" + minMaxLabel.isHidden = false + return + } + let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" if travelerCountLower == 0 { - self.presentErrorAlert(title: "Error", message: "You cannot have a minimum of 0 travelers!") + minTextField.displayError() + maxTextField.displayError() + minTextField.errorLabel.isHidden = true + maxTextField.errorLabel.isHidden = true + minMaxLabel.text = "You cannot have a minimum of 0 travelers!" + minMaxLabel.isHidden = false } else { NetworkManager.shared.currentRide.minTravelers = travelerCountLower NetworkManager.shared.currentRide.maxTravelers = travelerCountUpper NetworkManager.shared.currentRide.departureDatetime = dateFormatter.string(from: date) - NetworkManager.shared.currentRide.description = self.detailsTextField.text ?? "" + NetworkManager.shared.currentRide.description = detailsTextField.textView.text ?? "" - self.delegate?.didTapNext(navCtrl, nextViewController: nil) + delegate?.didTapNext(navCtrl, nextViewController: nil) } - self.summaryDelegate?.updateSummary() + summaryDelegate?.updateSummary() } setupStackView() @@ -107,8 +113,6 @@ class PostRideTripDetailsViewController: PostRideViewController { setupTimeView() setupTravelersView() setupDetailsView() - setupLabels() - configTextFields() setupNextButton(action: nextAction ?? UIAction(handler: { _ in return })) @@ -138,27 +142,21 @@ class PostRideTripDetailsViewController: PostRideViewController { travelersContainerView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() - make.height.equalTo(88) } stackView.addArrangedSubview(detailsTextField) - } private func setupDateView() { dateTextField.delegate = self - dateTextField.textColor = UIColor.scooped.offBlack - dateTextField.backgroundColor = .white - dateTextField.attributedPlaceholder = NSAttributedString( - string: "Departure date", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) + dateTextField.setup(title: "Departure date") datePicker.datePickerMode = .date datePicker.preferredDatePickerStyle = .wheels datePicker.minimumDate = Date() datePicker.isHighlighted = false datePicker.addTarget(self, action: #selector(updateDate), for: .valueChanged) - dateTextField.addTarget(self, action: #selector(updateDate), for: .touchDown) - dateTextField.inputView = datePicker + dateTextField.textField.addTarget(self, action: #selector(updateDate), for: .touchDown) + dateTextField.textField.inputView = datePicker dateTextField.snp.makeConstraints { make in make.top.equalToSuperview() @@ -171,33 +169,20 @@ class PostRideTripDetailsViewController: PostRideViewController { view.addSubview(calendarIcon) calendarIcon.snp.makeConstraints { make in - make.centerY.equalTo(dateTextField) + make.centerY.equalTo(dateTextField.textField) make.trailing.equalTo(dateTextField).inset(iconSpacing) make.size.equalTo(iconSize) } - - requiredLabel.text = "*required" - requiredLabel.font = UIFont(name: "Roboto", size: 12) - requiredLabel.textColor = UIColor.scooped.offBlack - view.addSubview(requiredLabel) - - requiredLabel.snp.makeConstraints { make in - make.leading.equalTo(dateTextField).inset(10) - make.top.equalTo(dateTextField.snp.bottom).inset(-4) - } } private func setupTimeView() { timeTextField.delegate = self - timeTextField.textColor = UIColor.scooped.offBlack - timeTextField.attributedPlaceholder = NSAttributedString( - string: "Departure time", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) - timeTextField.inputView = timePicker + timeTextField.setup(title: "Departure time") + timeTextField.textField.inputView = timePicker timePicker.datePickerMode = .time timePicker.preferredDatePickerStyle = .wheels timePicker.addTarget(self, action: #selector(updateTime), for: .valueChanged) - timeTextField.addTarget(self, action: #selector(updateTime), for: .touchDown) + timeTextField.textField.addTarget(self, action: #selector(updateTime), for: .touchDown) timeTextField.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() @@ -218,49 +203,49 @@ class PostRideTripDetailsViewController: PostRideViewController { } minTextField.delegate = self - minTextField.textColor = UIColor.scooped.offBlack - minTextField.attributedPlaceholder = NSAttributedString( - string: "Minimum", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) - minTextField.keyboardType = .numberPad + minTextField.setup(title: "Minimum", error: "Please complete") + minTextField.textField.keyboardType = .numberPad travelersContainerView.addSubview(minTextField) minTextField.snp.makeConstraints { make in make.leading.equalToSuperview() + make.top.equalTo(travelersLabel.snp.bottom).offset(24) make.width.equalTo((stackViewWidth - 15.0)/2) make.height.equalTo(textFieldHeight) - make.top.equalTo(travelersLabel.snp.bottom).inset(-8) } maxTextField.delegate = self - maxTextField.textColor = UIColor.scooped.offBlack - maxTextField.attributedPlaceholder = NSAttributedString( - string: "Maximum", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) - maxTextField.keyboardType = .numberPad + maxTextField.setup(title: "Maximum", error: "Please complete") + maxTextField.textField.keyboardType = .numberPad travelersContainerView.addSubview(maxTextField) maxTextField.snp.makeConstraints { make in + make.top.equalTo(minTextField) make.leading.equalTo(minTextField.snp.trailing).inset(-15) make.trailing.equalToSuperview() make.height.equalTo(textFieldHeight) - make.top.equalTo(travelersLabel.snp.bottom).inset(-8) + } + + minMaxLabel.text = "The minimum must be less than the maximum" + minMaxLabel.font = .systemFont(ofSize: 12) + minMaxLabel.textColor = UIColor.scooped.errorRed + minMaxLabel.isHidden = true + travelersContainerView.addSubview(minMaxLabel) + + minMaxLabel.snp.makeConstraints { make in + make.leading.equalTo(minTextField).inset(10) + make.top.equalTo(minTextField.snp.bottom).offset(-5) + make.bottom.equalToSuperview() } } private func setupDetailsView() { detailsTextField.delegate = self - detailsTextField.text = "Details" - detailsTextField.textContainerInset = UIEdgeInsets(top: 18.5, left: 16, bottom: 18.5, right: 16) - detailsTextField.textColor = .black - detailsTextField.layer.borderWidth = textFieldBorderWidth - detailsTextField.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - detailsTextField.layer.cornerRadius = textFieldCornerRadius - detailsTextField.font = .systemFont(ofSize: 16) + detailsTextField.setup(title: "Details") detailsTextField.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() - make.height.equalTo(textFieldHeight) + make.height.equalTo(82) } detailsExample.text = "ie. splitting gas, departure time, etc." @@ -273,69 +258,7 @@ class PostRideTripDetailsViewController: PostRideViewController { make.top.equalTo(detailsTextField.snp.bottom).inset(-4) } } - - private func configTextFields() { - [dateTextField, timeTextField, minTextField, maxTextField].forEach { text in - text.layer.borderWidth = textFieldBorderWidth - text.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - text.layer.cornerRadius = textFieldCornerRadius - text.font = textFieldFont - } - } - - private func setupLabels() { - let labelLeading = 10 - let labelTop = 8 - [dateLabel, timeLabel, minLabel, maxLabel, detailsLabel].forEach { label in - label.font = .systemFont(ofSize: 12) - label.textColor = UIColor.scooped.scoopDarkGreen - label.backgroundColor = .white - label.textAlignment = .center - label.isHidden = true - view.addSubview(label) - } - - dateLabel.text = "Departure date" - dateLabel.snp.makeConstraints { make in - make.top.equalTo(dateTextField).inset(-labelTop) - make.leading.equalTo(dateTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(91) - } - - timeLabel.text = "Departure time" - timeLabel.snp.makeConstraints { make in - make.top.equalTo(timeTextField).inset(-labelTop) - make.leading.equalTo(timeTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(91) - } - - minLabel.text = "Minimum" - minLabel.snp.makeConstraints { make in - make.top.equalTo(minTextField).inset(-labelTop) - make.leading.equalTo(minTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(54) - } - - maxLabel.text = "Maximum" - maxLabel.snp.makeConstraints { make in - make.top.equalTo(maxTextField).inset(-labelTop) - make.leading.equalTo(maxTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(57) - } - - detailsLabel.text = "Details" - detailsLabel.snp.makeConstraints { make in - make.top.equalTo(detailsTextField).inset(-labelTop) - make.leading.equalTo(detailsTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(42) - } - } - + // MARK: - Helper Functions private func getTravelDate() -> Date? { @@ -357,13 +280,13 @@ class PostRideTripDetailsViewController: PostRideViewController { @objc private func updateDate() { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium - dateTextField.text = dateFormatter.string(from: datePicker.date) + dateTextField.textField.text = dateFormatter.string(from: datePicker.date) } @objc private func updateTime() { let timeFormatter = DateFormatter() timeFormatter.timeStyle = .short - timeTextField.text = timeFormatter.string(from: timePicker.date) + timeTextField.textField.text = timeFormatter.string(from: timePicker.date) } } @@ -373,7 +296,7 @@ class PostRideTripDetailsViewController: PostRideViewController { extension PostRideTripDetailsViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if textField == maxTextField || textField == minTextField { + if textField == maxTextField.textField || textField == minTextField.textField { let numOnly = CharacterSet.decimalDigits let characters = CharacterSet(charactersIn: string) // Source: https://stackoverflow.com/a/31363255/5278889 @@ -386,39 +309,50 @@ extension PostRideTripDetailsViewController: UITextFieldDelegate { return false } } - + func textFieldDidBeginEditing(_ textField: UITextField) { - textField.layer.borderWidth = 2 - textField.layer.borderColor = UIColor.scooped.scoopDarkGreen.cgColor - textField.placeholder = "" - if textField == dateTextField { - dateLabel.textColor = UIColor.scooped.scoopDarkGreen - dateLabel.isHidden = false - } else if textField == timeTextField { - timeLabel.textColor = UIColor.scooped.scoopDarkGreen - timeLabel.isHidden = false - } else if textField == maxTextField { - maxLabel.textColor = UIColor.scooped.scoopDarkGreen - maxLabel.isHidden = false - } else { - minLabel.textColor = UIColor.scooped.scoopDarkGreen - minLabel.isHidden = false + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + associatedView.hidesLabel(isHidden: false) + } + + if textField == minTextField.textField || textField == maxTextField.textField { + minMaxLabel.isHidden = true } } - + func textFieldDidEndEditing(_ textField: UITextField) { - textField.layer.borderWidth = 1 - textField.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - if textField == dateTextField { - dateLabel.textColor = UIColor.scooped.textFieldBorderColor - } else if textField == timeTextField { - timeLabel.textColor = UIColor.scooped.textFieldBorderColor - } else if textField == maxTextField { - maxLabel.textColor = UIColor.scooped.textFieldBorderColor - } else { - minLabel.textColor = UIColor.scooped.textFieldBorderColor + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: false) + if textField.text?.isEmpty ?? true { + associatedView.hidesLabel(isHidden: true) + } + } + + var responses: [String] = [] + [dateTextField, timeTextField, minTextField, maxTextField, ].forEach { textField in + responses.append(textField.textField.text ?? "") + } + + setNextButtonColor(disabled: !textFieldsComplete(texts: responses)) + } + + func textFieldDidChangeSelection(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + } + + if textField == minTextField.textField || textField == maxTextField.textField { + minMaxLabel.isHidden = true } } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.endEditing(true) + } } @@ -427,35 +361,17 @@ extension PostRideTripDetailsViewController: UITextFieldDelegate { extension PostRideTripDetailsViewController: UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { - if textView.textColor == UIColor.black { - textView.text = nil - textView.textColor = UIColor.scooped.offBlack - } - textView.layer.borderWidth = 2 - textView.layer.borderColor = UIColor.scooped.scoopDarkGreen.cgColor - detailsLabel.textColor = UIColor.scooped.scoopDarkGreen - detailsLabel.isHidden = false - - textView.snp.removeConstraints() - textView.snp.makeConstraints { make in - make.leading.trailing.equalToSuperview() - make.height.equalTo(82) + if let paddedTextView = textView as? PaddedTextView, + let associatedView = paddedTextView.associatedView as? LabeledTextView { + associatedView.labeledTextView(isSelected: true) } } - + func textViewDidEndEditing(_ textView: UITextView) { - textView.layer.borderWidth = 1 - textView.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - detailsLabel.textColor = UIColor.scooped.textFieldBorderColor - } - - func textFieldDidChangeSelection(_ textField: UITextField) { - var responses: [String] = [] - [dateTextField, timeTextField, minTextField, maxTextField].forEach { textField in - responses.append(textField.text ?? "") + if let paddedTextView = textView as? PaddedTextView, + let associatedView = paddedTextView.associatedView as? LabeledTextView { + associatedView.labeledTextView(isSelected: false) } - - setNextButtonColor(disabled: !textFieldsComplete(texts: responses)) } } diff --git a/Scoop/SearchRidesViewController.swift b/Scoop/SearchRidesViewController.swift index 0d45345..f75c61a 100644 --- a/Scoop/SearchRidesViewController.swift +++ b/Scoop/SearchRidesViewController.swift @@ -11,15 +11,12 @@ import UIKit class SearchRidesViewController: UIViewController { // MARK: - Views - - private let arrivalLabel = UILabel() - private let arrivalTextField = ImageTextField() + + private let arrivalTextField = LabeledTextField(isShifted: true) private let calendarIconImageView = UIImageView() private let datePicker = UIDatePicker() - private let departureDateLabel = UILabel() - private let departureDateTextField = OnboardingTextField() - private let departureLabel = UILabel() - private let departureTextField = ImageTextField() + private let departureDateTextField = LabeledTextField() + private let departureTextField = LabeledTextField(isShifted: true) private let findTripsButton = UIButton() private let stackView = UIStackView() @@ -40,7 +37,6 @@ class SearchRidesViewController: UIViewController { setupStackView() setupLocationTextFields() setupButton() - setupLabels() } // MARK: - Setup View Functions @@ -98,33 +94,43 @@ class SearchRidesViewController: UIViewController { let textFieldBorderWidth = 1.0 let textFieldCornerRadius = 4.0 let textFieldHeight = 56 - - departureTextField.textField.textColor = UIColor.scooped.offBlack + departureTextField.textField.delegate = self - departureTextField.textField.attributedPlaceholder = NSAttributedString( - string: "Departure location", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) + departureTextField.setup(title: "Departure location") + + // Sets up the icon inside the text field. + let iconContainer = UIView(frame: CGRect(x: 0, y: 0, width: 25, height: 15)) + let departureIcon = UIImageView(frame: CGRect(x: 10, y: -2.5, width: 20, height: 20)) + departureIcon.image = UIImage(named: "locationIcon") + departureIcon.contentMode = .scaleAspectFit + iconContainer.addSubview(departureIcon) + + departureTextField.textField.leftView = iconContainer; + departureTextField.textField.leftViewMode = UITextField.ViewMode.always + departureTextField.textField.leftViewMode = .always departureTextField.textField.addTarget(self, action: #selector(presentDepartureSearch), for: .touchDown) - departureTextField.imageView.image = UIImage(named: "locationIcon") stackView.addArrangedSubview(departureTextField) - - arrivalTextField.textField.textColor = UIColor.scooped.offBlack + arrivalTextField.textField.delegate = self - arrivalTextField.textField.attributedPlaceholder = NSAttributedString( - string: "Arrival location", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) + arrivalTextField.setup(title: "Arrival location") + + // Sets up the icon inside the text field. + let iconContainer2 = UIView(frame: CGRect(x: 0, y: 0, width: 25, height: 15)) + let arrivalIcon = UIImageView(frame: CGRect(x: 10, y: -2.5, width: 20, height: 20)) + arrivalIcon.image = UIImage(named: "destinationIcon") + arrivalIcon.contentMode = .scaleAspectFit + iconContainer2.addSubview(arrivalIcon) + + arrivalTextField.textField.leftView = iconContainer2; + arrivalTextField.textField.leftViewMode = .always arrivalTextField.textField.addTarget(self, action: #selector(presentArrivalSearch), for: .touchDown) - arrivalTextField.imageView.image = UIImage(named: "destinationIcon") stackView.addArrangedSubview(arrivalTextField) - - departureDateTextField.textColor = UIColor.scooped.offBlack + departureDateTextField.delegate = self - departureDateTextField.attributedPlaceholder = NSAttributedString( - string: "Departure date", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.scooped.offBlack]) - departureDateTextField.inputView = datePicker - departureDateTextField.addTarget(self, action: #selector(updateDate), for: .touchDown) - departureDateTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingDidEnd) + departureDateTextField.setup(title: "Departure date") + departureDateTextField.textField.inputView = datePicker + departureDateTextField.textField.addTarget(self, action: #selector(updateDate), for: .touchDown) + departureDateTextField.textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingDidEnd) stackView.addArrangedSubview(departureDateTextField) datePicker.datePickerMode = .date @@ -136,15 +142,12 @@ class SearchRidesViewController: UIViewController { view.addSubview(calendarIconImageView) calendarIconImageView.snp.makeConstraints { make in - make.centerY.equalTo(departureDateTextField) + make.centerY.equalTo(departureDateTextField.textField) make.trailing.equalTo(departureDateTextField).inset(12) make.size.equalTo(24) } [departureTextField, arrivalTextField, departureDateTextField].forEach { text in - text.layer.borderWidth = textFieldBorderWidth - text.layer.borderColor = UIColor.scooped.textFieldBorderColor.cgColor - text.layer.cornerRadius = textFieldCornerRadius text.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.height.equalTo(textFieldHeight) @@ -170,78 +173,42 @@ class SearchRidesViewController: UIViewController { } } - private func setupLabels() { - let labelLeading = 10 - let labelTop = 8 - - [departureLabel, arrivalLabel, departureDateLabel].forEach { label in - label.font = .systemFont(ofSize: 12) - label.textColor = UIColor.scooped.textFieldBorderColor - label.backgroundColor = .white - label.textAlignment = .center - label.isHidden = true - view.addSubview(label) - } - - departureLabel.text = "Departure location" - departureLabel.snp.makeConstraints { make in - make.top.equalTo(departureTextField).inset(-labelTop) - make.leading.equalTo(departureTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(120) - } - - arrivalLabel.text = "Arrival location" - arrivalLabel.snp.makeConstraints { make in - make.top.equalTo(arrivalTextField).inset(-labelTop) - make.leading.equalTo(arrivalTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(99) - } - - departureDateLabel.text = "Departure date" - departureDateLabel.snp.makeConstraints { make in - make.top.equalTo(departureDateTextField).inset(-labelTop) - make.leading.equalTo(departureDateTextField).inset(labelLeading) - make.height.equalTo(16) - make.width.equalTo(99) - } - } - // MARK: - Helper Functions @objc private func presentDepartureSearch() { let depatureVC = DepartureSearchViewController() depatureVC.delegate = self navigationController?.pushViewController(depatureVC, animated: true) - departureLabel.isHidden = false } @objc private func presentArrivalSearch() { let arrivalVC = ArrivalSearchViewController() arrivalVC.delegate = self navigationController?.pushViewController(arrivalVC, animated: true) - arrivalLabel.isHidden = false } @objc private func updateDate() { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium - departureDateTextField.text = dateFormatter.string(from: datePicker.date) - departureDateLabel.isHidden = false + departureDateTextField.textField.text = dateFormatter.string(from: datePicker.date) } @objc private func presentMatches() { - findTripsButton.isEnabled = false + + [departureTextField, arrivalTextField, departureDateTextField].forEach { textField in + if (textField.textField.text ?? "").isEmpty { + textField.displayError() + } + } + findTripsButton.backgroundColor = UIColor.scooped.disabledGreen guard let departure = self.departureTextField.textField.text, !departure.isEmpty, let arrival = self.arrivalTextField.textField.text, !arrival.isEmpty, let departureID = departurePlace?.placeID, - let dateString = departureDateTextField.text, !dateString.isEmpty, - let arrivalID = arrivalPlace?.placeID else { - presentErrorAlert(title: "Error", message: "Please complete all fields.") - return - } + let dateString = departureDateTextField.textField.text, !dateString.isEmpty, + let arrivalID = arrivalPlace?.placeID else { return } + + findTripsButton.isEnabled = false tripDate = formatDate(dateToConvert: self.datePicker.date) NetworkManager.shared.searchLocation(depatureDate: tripDate, startLocation: departureID, endLocation: arrivalID) { [weak self] response in @@ -264,7 +231,7 @@ class SearchRidesViewController: UIViewController { !text1.isEmpty, let text2 = arrivalTextField.textField.text, !text2.isEmpty, - let text3 = departureDateTextField.text, + let text3 = departureDateTextField.textField.text, !text3.isEmpty else { findTripsButton.backgroundColor = UIColor.scooped.disabledGreen return @@ -289,6 +256,35 @@ extension SearchRidesViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { return false } + + func textFieldDidBeginEditing(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + associatedView.hidesLabel(isHidden: false) + } + } + + func textFieldDidEndEditing(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: false) + if textField.text?.isEmpty ?? true { + associatedView.hidesLabel(isHidden: true) + } + } + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.endEditing(true) + } + + func textFieldDidChangeSelection(_ textField: UITextField) { + if let onboardingTextField = textField as? OnboardingTextField, + let associatedView = onboardingTextField.associatedView as? LabeledTextField { + associatedView.labeledTextField(isSelected: true) + } + } } @@ -301,10 +297,14 @@ extension SearchRidesViewController: SearchInitialViewControllerDelegate { departurePlace = location departureTextField.textField.text = location.name self.textFieldDidChange(sender: departureTextField.textField) + departureTextField.hidesLabel(isHidden: false) + departureTextField.labeledTextField(isSelected: false) } else if viewController is ArrivalSearchViewController { arrivalPlace = location arrivalTextField.textField.text = location.name self.textFieldDidChange(sender: arrivalTextField.textField) + arrivalTextField.hidesLabel(isHidden: false) + arrivalTextField.labeledTextField(isSelected: false) } } diff --git a/Scoop/Supporting/Utilities/Extension+UIColor.swift b/Scoop/Supporting/Utilities/Extension+UIColor.swift index fdfedf2..eda478e 100644 --- a/Scoop/Supporting/Utilities/Extension+UIColor.swift +++ b/Scoop/Supporting/Utilities/Extension+UIColor.swift @@ -17,6 +17,8 @@ extension UIColor { let darkerGreen = UIColor(red: 0, green: 0.42, blue: 0.33, alpha: 1) let disabledGreen = UIColor(red: 219/255, green: 229/255, blue: 223/255, alpha: 1) let disabledGrey = UIColor(red: 136/255, green: 153/255, blue: 155/255, alpha: 1) + let errorRed = UIColor(red: 186/255, green: 26/255, blue: 26/255, alpha: 1) + let errorRed2 = UIColor(red: 105/255, green: 0/255, blue: 5/255, alpha: 1) let labelGray = UIColor(red: 112/255, green: 112/255, blue: 112/255, alpha: 1) let linearGradient = UIColor(red: 1, green: 1, blue: 1, alpha: 1) let mutedGrey = UIColor(red: 106/255, green: 115/255, blue: 125/255, alpha: 1) diff --git a/Scoop/View/ImageTextField.swift b/Scoop/View/ImageTextField.swift index cebaaa6..d1d1a64 100644 --- a/Scoop/View/ImageTextField.swift +++ b/Scoop/View/ImageTextField.swift @@ -8,7 +8,7 @@ import UIKit class ImageTextField: UIView { - + let textField = ShiftedRightTextField() let imageView = UIImageView() @@ -30,10 +30,10 @@ class ImageTextField: UIView { make.size.equalTo(20) } } - + private func setupInfoLabel() { addSubview(textField) - + textField.snp.makeConstraints { make in make.leading.equalToSuperview().inset(5) make.trailing.equalToSuperview() @@ -41,10 +41,10 @@ class ImageTextField: UIView { make.height.equalTo(56) } } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + } diff --git a/Scoop/View/LabeledTextField.swift b/Scoop/View/LabeledTextField.swift index a6d6c37..c08b3af 100644 --- a/Scoop/View/LabeledTextField.swift +++ b/Scoop/View/LabeledTextField.swift @@ -12,20 +12,40 @@ class LabeledTextField: UIView { weak var delegate: UITextFieldDelegate? // MARK: - Views - + + let errorLabel = UILabel() private let nameLabel = UILabel() - let textField = OnboardingTextField() - + var textField = OnboardingTextField() + + // MARK: - Initializers + + init(isShifted: Bool = false) { + if isShifted { + textField = OnboardingTextField(isShifted: true) + } + + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - Setup Views - func setup(title: String, placeholder: String? = nil) { + func setup(title: String, placeholder: String? = nil, error: String? = nil) { var text = title + var errorText = "Please complete this field." if let placeholder = placeholder { text = placeholder } + + if let error = error { + errorText = error + } setupTextField(placeholder: text) - setupLabel(title: title) + setupLabels(title: title, error: errorText) } private func setupTextField(placeholder: String) { @@ -38,19 +58,19 @@ class LabeledTextField: UIView { textField.backgroundColor = .white textField.layer.borderWidth = 1 textField.layer.cornerRadius = 4 - textField.layer.borderColor = UIColor.black.cgColor + textField.layer.borderColor = UIColor.scooped.mutedGrey.cgColor textField.delegate = self.delegate textField.isUserInteractionEnabled = true textField.associatedView = self addSubview(textField) textField.snp.makeConstraints { make in - make.leading.trailing.bottom.equalToSuperview() + make.leading.trailing.equalToSuperview() make.height.equalTo(56) } } - private func setupLabel(title: String) { + private func setupLabels(title: String, error: String) { nameLabel.text = " \(title) " nameLabel.font = .systemFont(ofSize: 12) nameLabel.textColor = UIColor.scooped.mutedGrey @@ -62,21 +82,54 @@ class LabeledTextField: UIView { make.leading.equalToSuperview().inset(16) make.centerY.equalTo(textField.snp.top) } + + errorLabel.text = error + errorLabel.font = .systemFont(ofSize: 12) + errorLabel.textColor = UIColor.scooped.errorRed + errorLabel.backgroundColor = .white + errorLabel.isHidden = true + addSubview(errorLabel) + + errorLabel.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.top.equalTo(textField.snp.bottom).offset(5) + make.bottom.equalToSuperview() + } } // MARK: - Helper Functions func labeledTextField(isSelected: Bool) { + errorLabel.isHidden = true + if isSelected { nameLabel.textColor = UIColor.scooped.scoopDarkGreen textField.layer.borderWidth = 2 textField.layer.borderColor = UIColor.scooped.scoopDarkGreen.cgColor } else { - nameLabel.textColor = UIColor.scooped.mutedGrey textField.layer.borderWidth = 1 - textField.layer.borderColor = UIColor.black.cgColor + + if let text = textField.text, !text.isEmpty { + nameLabel.textColor = UIColor.scooped.offBlack + textField.layer.borderColor = UIColor.black.cgColor + } else { + nameLabel.textColor = UIColor.scooped.mutedGrey + textField.layer.borderColor = UIColor.scooped.mutedGrey.cgColor + } } } + + func displayError() { + nameLabel.textColor = UIColor.scooped.errorRed2 + textField.layer.borderColor = UIColor.scooped.errorRed.cgColor + errorLabel.isHidden = false + } + + func hideError() { + nameLabel.textColor = (textField.text ?? "").isEmpty ? UIColor.scooped.mutedGrey : UIColor.scooped.offBlack + textField.layer.borderColor = (textField.text ?? "").isEmpty ? UIColor.scooped.mutedGrey.cgColor : UIColor.black.cgColor + errorLabel.isHidden = true + } func hidesLabel(isHidden: Bool) { nameLabel.isHidden = isHidden diff --git a/Scoop/View/LabeledTextView.swift b/Scoop/View/LabeledTextView.swift index a63cbf5..07d76f6 100644 --- a/Scoop/View/LabeledTextView.swift +++ b/Scoop/View/LabeledTextView.swift @@ -19,22 +19,17 @@ class LabeledTextView: UIView { // MARK: - Setup Views func setup(title: String, placeholder: String? = nil) { - var text = title - if let placeholder = placeholder { - text = placeholder - } - - setupTextField(placeholder: text) + setupTextField() setupLabel(title: title) } - private func setupTextField(placeholder: String) { + private func setupTextField() { textView.font = .systemFont(ofSize: 16) textView.textColor = .black textView.backgroundColor = .white textView.layer.borderWidth = 1 textView.layer.cornerRadius = 4 - textView.layer.borderColor = UIColor.black.cgColor + textView.layer.borderColor = UIColor.scooped.mutedGrey.cgColor textView.delegate = self.delegate textView.isUserInteractionEnabled = true textView.isSelectable = true @@ -65,13 +60,20 @@ class LabeledTextView: UIView { func labeledTextView(isSelected: Bool) { if isSelected { + nameLabel.isHidden = false nameLabel.textColor = UIColor.scooped.scoopDarkGreen textView.layer.borderWidth = 2 textView.layer.borderColor = UIColor.scooped.scoopDarkGreen.cgColor } else { - nameLabel.textColor = UIColor.scooped.mutedGrey textView.layer.borderWidth = 1 - textView.layer.borderColor = UIColor.black.cgColor + + if let text = textView.text, !text.isEmpty { + nameLabel.textColor = UIColor.black + textView.layer.borderColor = UIColor.black.cgColor + } else { + nameLabel.textColor = UIColor.scooped.mutedGrey + textView.layer.borderColor = UIColor.scooped.mutedGrey.cgColor + } } } diff --git a/Scoop/View/OnboardingTextField.swift b/Scoop/View/OnboardingTextField.swift index 6aa53c4..d016555 100644 --- a/Scoop/View/OnboardingTextField.swift +++ b/Scoop/View/OnboardingTextField.swift @@ -8,11 +8,29 @@ import UIKit class OnboardingTextField: UITextField { - - let padding = UIEdgeInsets(top: 18.5, left: 16, bottom: 18.5, right: 16) + + // MARK: - Data + + var padding = UIEdgeInsets(top: 18.5, left: 16, bottom: 18.5, right: 16) weak var associatedView: UIView? + // MARK: - Initializers + + init(isShifted: Bool = false) { + if isShifted { + padding = UIEdgeInsets(top: 12, left: 38, bottom: 12, right: 60) + } + + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - TextField Bounds + override open func textRect(forBounds bounds: CGRect) -> CGRect { return bounds.inset(by: padding) }