UITextField is partially hidden by Keyboard when opened - swift

I am attempting to create a collection of UITextField elements. I'd like the next button on the keyboard to skip to the next field and if that field is hidden from view by the keyboard, scroll it into view.
This is my attempt. It works apart from 1 aspect.
When dismissing the keyboard and then selecting another (or the same) field, the text input is partially hidden by the keyboard (see attached gif).
The meat and potatoes is within the ViewController extension.
class ViewController: UIViewController {
var activeField: UITextField?
var lastOffset: CGPoint!
var keyboardHeight: CGFloat!
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
let totalFieldCount = 25
for i in 1...totalFieldCount {
let textField = createTextField(self, placeholder: "Field #\(i)", type: .default)
textField.returnKeyType = i < totalFieldCount ? .next : .done
textField.tag = i
scrollViewContainer.addArrangedSubview(textField)
}
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor),
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
scrollView.keyboardDismissMode = .interactive
}
func createTextField(_ delegate: UITextFieldDelegate?, placeholder: String, type: UIKeyboardType, isSecureEntry: Bool = false) -> UITextField {
let tf = UITextField(frame: .zero)
tf.placeholder = placeholder
tf.backgroundColor = .init(white: 0, alpha: 0.03)
tf.borderStyle = .roundedRect
tf.font = .systemFont(ofSize: 14)
tf.keyboardType = type
tf.autocapitalizationType = .none
tf.autocorrectionType = .no
tf.isSecureTextEntry = isSecureEntry
tf.heightAnchor.constraint(equalToConstant: 40).isActive = true
if let delegate = delegate {
tf.delegate = delegate
}
return tf
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
activeField = textField
lastOffset = self.scrollView.contentOffset
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
} else {
activeField?.resignFirstResponder()
activeField = nil
}
return true
}
}
extension ViewController {
#objc func keyboardWillShow(notification: NSNotification) {
guard keyboardHeight == nil else { return }
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
keyboardHeight = keyboardSize.height
UIView.animate(withDuration: 0.3, animations: {
self.scrollView.contentInset.bottom = self.keyboardHeight
})
guard let activeField = activeField else { return }
let distanceToBottom = self.scrollView.frame.size.height - (activeField.frame.origin.y) - (activeField.frame.size.height)
let collapseSpace = keyboardHeight - distanceToBottom
guard collapseSpace > 0 else { return }
UIView.animate(withDuration: 0.3, animations: {
self.scrollView.contentOffset = CGPoint(x: self.lastOffset.x, y: collapseSpace + 10)
})
}
}
#objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.3) {
self.scrollView.contentOffset = self.lastOffset
self.scrollView.contentInset.bottom = 0
}
keyboardHeight = nil
}
}

Replace keyboardFrameBeginUserInfoKey with keyboardFrameEndUserInfoKey

Related

Error Thread 1: EXC_BAD_ACCESS (code=1, address=0x30)

Please tell me the application crashes with an error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x30)
when clicking on the button for reminders for a table cell (switching to a new View Controller).
The error appears in the class when creating a UILabel in the line let label = UILabel ():
let oneLabel: UILabel = {
let label = UILabel()
if (UIDevice.current.userInterfaceIdiom == .pad) {
label.font = UIFont.systemFont(ofSize: 32, weight: .semibold)
label.numberOfLines = 4
}
else {
label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
label.numberOfLines = 1
}
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Here is the code from another class that transitions to the View Controller, for which the UILabel is created:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let alarm = UIContextualAction(
style: .normal,
title: "",
handler: {(_, _, completion) in
self.notificationCenter.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
guard granted else { return DispatchQueue.main.async { self.createAlertForNotifications() } }
self.notificationCenter.getNotificationSettings { (settings) in
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
let vc = OneViewController()
self.parentController!.navigationController?.setViewControllers([vc], animated: false)
}
completion(true)
}
}
}
)
alarm.image = ListImages.alarmImage
alarm.backgroundColor = .systemPurple
return UISwipeActionsConfiguration(actions: [alarm])
}
Sometimes this error appears, sometimes not, and it may appear for the second UILabel, which is created after the first. How can you fix this error?
Sometimes I get error in let dataPicker = UIDatePicker()
Here you go to the OneViewController:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let oneObject = isFiltering ? filteredObjects[indexPath.row] : listObjects[indexPath.row]
let alarm = UIContextualAction(
style: .normal,
title: "",
handler: {(_, _, completion) in
self.notificationCenter.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
guard granted else { return DispatchQueue.main.async { self.createAlertForNotifications() } }
self.notificationCenter.getNotificationSettings { (settings) in
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
let vc = OneViewController()
ListNameLabel.oneText = oneObject.name
self.parentController!.navigationController?.setViewControllers([vc], animated: false)
}
completion(true)
}
}
}
)
alarm.image = ListImages.alarmImage
alarm.backgroundColor = .systemPurple
return UISwipeActionsConfiguration(actions: [alarm])
}
This is class with UILabel:
import UIKit
class MainView: UIView {
let oneLabel: UILabel = {
let label = UILabel()
if (UIDevice.current.userInterfaceIdiom == .pad) {
label.font = UIFont.systemFont(ofSize: 32, weight: .semibold)
label.numberOfLines = 4
}
else {
label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
label.numberOfLines = 1
}
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let sheduleTimeDataPicker: UIDatePicker = {
let dataPicker = UIDatePicker()
if #available(iOS 14, *) {
dataPicker.preferredDatePickerStyle = .inline
}
else {
dataPicker.preferredDatePickerStyle = .compact
}
dataPicker.datePickerMode = .dateAndTime
dataPicker.date = Date()
dataPicker.minimumDate = Date()
dataPicker.translatesAutoresizingMaskIntoConstraints = false
return dataPicker
}()
let oneView: UIView = {
let view = UIView()
view.backgroundColor = .systemGray5
view.layer.cornerRadius = 15
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(oneView)
oneView.addSubview(oneLabel)
oneView.addSubview(sheduleTimeDataPicker)
if (UIDevice.current.userInterfaceIdiom == .pad) {
// oneLabel constraints
oneLabel.leadingAnchor.constraint(equalTo: oneView.leadingAnchor, constant: 20).isActive = true
oneLabel.trailingAnchor.constraint(equalTo: oneView.trailingAnchor, constant: -20).isActive = true
oneLabel.topAnchor.constraint(equalTo: oneView.topAnchor, constant: 20).isActive = true
oneLabel.heightAnchor.constraint(equalToConstant: 120).isActive = true
// sheduleTimeDataPicker constraints
sheduleTimeDataPicker.centerXAnchor.constraint(equalTo: oneView.centerXAnchor).isActive = true
sheduleTimeDataPicker.topAnchor.constraint(equalTo: oneLabel.bottomAnchor).isActive = true
}
else {
// oneLabel constraints
oneLabel.leadingAnchor.constraint(equalTo: oneView.leadingAnchor, constant: 10).isActive = true
oneLabel.trailingAnchor.constraint(equalTo: oneView.trailingAnchor, constant: -10).isActive = true
oneLabel.topAnchor.constraint(equalTo: oneView.topAnchor, constant: 5).isActive = true
oneLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
// sheduleTimeDataPicker constraints
sheduleTimeDataPicker.leadingAnchor.constraint(equalTo: oneView.leadingAnchor, constant: 10).isActive = true
sheduleTimeDataPicker.trailingAnchor.constraint(equalTo: oneView.trailingAnchor, constant: -10).isActive = true
sheduleTimeDataPicker.topAnchor.constraint(equalTo: oneLabel.bottomAnchor).isActive = true
}
// oneView constraints
oneView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
oneView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10).isActive = true
oneView.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
oneView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10).isActive = true
}
func setContentView(content: OneModel) {
self.oneLabel.text = content.oneName
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is class OneModel:
import UIKit
struct ListNameLabel {
static var oneText = ""
}
struct OneModel {
var oneName: String
static func fetchView() -> OneModel {
return OneModel(oneName: ListNameLabel.oneText)
}
}
This is class OneViewController:
import UIKit
import UserNotifications
class OneViewController: UIViewController {
var mainView = MainView()
let notificationCenter = UNUserNotificationCenter.current()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupNavigationBar()
setupView()
}
// MARK: NavigationBar
private func setupNavigationBar() {
let backBarButtonItem = UIBarButtonItem()
backBarButtonItem.image = ListImages.chevronImage
backBarButtonItem.action = #selector(backBarButtonItemTapped)
backBarButtonItem.target = self
navigationItem.leftBarButtonItem = backBarButtonItem
navigationItem.title = ""
}
// MARK: View
private func setupView() {
view.backgroundColor = .systemBackground
view.addSubview(mainView)
mainView.translatesAutoresizingMaskIntoConstraints = false
let guide = self.view.safeAreaLayoutGuide
// mainView constraints
mainView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
mainView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
mainView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
mainView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
// fetch
mainView.setContentView(content: OneModel.fetchView())
}
}
// MARK: Back
extension OneViewController {
#objc func backBarButtonItemTapped() {
let vc = TwoViewController()
self.navigationController?.setViewControllers([vc], animated: false)
}
}
// MARK: UNUserNotificationCenterDelegate
extension OneViewController: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.banner, .sound])
print(#function)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print(#function)
}
}

UITextView with Adjustable Font Size

I tried to make an editable uitextview with centered text that can have a maximum of 2 lines and that automatically adjusts its font size in order to fit its text within a fixed width and height.
My solution: Type some text in a UITextView, automatically copy that text and paste it in a uilabel that itself automatically adjusts its font size perfectly, and then retrieve the newly adjusted font size of the uilabel and set that size on the UITextView text.
I have spent about a month on this and repeatedly failed. I can't find a way to make it work. My attempted textview below glitches some letters out and hides large portions of out-of-bounds text instead of resizing everything. Please help me stack overflow Gods.
My attempt:
import UIKit
class TextViewViewController: UIViewController{
private let editableUITextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 20)
tv.text = "Delete red text, and type here"
tv.backgroundColor = .clear
tv.textAlignment = .center
tv.textContainer.maximumNumberOfLines = 2
tv.textContainer.lineBreakMode = .byWordWrapping
tv.textColor = .red
return tv
}()
private let correctTextSizeLabel: UILabel = {
let tv = UILabel()
tv.font = UIFont.systemFont(ofSize: 20)
tv.backgroundColor = .clear
tv.text = "This is properly resized"
tv.adjustsFontSizeToFitWidth = true
tv.lineBreakMode = .byTruncatingTail
tv.numberOfLines = 2
tv.textAlignment = .center
tv.textColor = .green
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(correctTextSizeLabel)
view.addSubview(editableUITextView)
editableUITextView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
editableUITextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
editableUITextView.heightAnchor.constraint(equalToConstant: 150).isActive = true
editableUITextView.widthAnchor.constraint(equalToConstant: 150).isActive = true
editableUITextView.translatesAutoresizingMaskIntoConstraints = false
editableUITextView.delegate = self
correctTextSizeLabel.leftAnchor.constraint(equalTo: editableUITextView.leftAnchor).isActive = true
correctTextSizeLabel.rightAnchor.constraint(equalTo: editableUITextView.rightAnchor).isActive = true
correctTextSizeLabel.topAnchor.constraint(equalTo: editableUITextView.topAnchor).isActive = true
correctTextSizeLabel.bottomAnchor.constraint(equalTo: editableUITextView.bottomAnchor).isActive = true
correctTextSizeLabel.translatesAutoresizingMaskIntoConstraints = false
editableUITextView.isScrollEnabled = false
}
func getApproximateAdjustedFontSizeOfLabel(label: UILabel) -> CGFloat {
if label.adjustsFontSizeToFitWidth == true {
var currentFont: UIFont = label.font
let originalFontSize = currentFont.pointSize
var currentSize: CGSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: currentFont])
while currentSize.width > label.frame.size.width * 2 && currentFont.pointSize > (originalFontSize * label.minimumScaleFactor) {
currentFont = currentFont.withSize(currentFont.pointSize - 1)
currentSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: currentFont])
}
return currentFont.pointSize
} else {
return label.font.pointSize
}
}
}
//MARK: - UITextViewDelegate
extension TextViewViewController : UITextViewDelegate {
private func textViewShouldBeginEditing(_ textView: UITextView) {
textView.becomeFirstResponder()
}
func textViewDidBeginEditing(_ textView: UITextView) {
}
func textViewDidEndEditing(_ textView: UITextView) {
}
func textViewDidChange(_ textView: UITextView) {
textView.becomeFirstResponder()
self.correctTextSizeLabel.text = textView.text
self.correctTextSizeLabel.isHidden = false
let estimatedTextSize = self.getApproximateAdjustedFontSizeOfLabel(label: self.correctTextSizeLabel)
print("estimatedTextSize: ",estimatedTextSize)
self.editableUITextView.font = UIFont.systemFont(ofSize: estimatedTextSize)
}
}
UITextField's have the option to automatically adjust font size to fit a fixed width but they only allow 1 line of text, I need it to have a maximum of 2. UILabel's solve this problem perfectly but they aren't editable.
After some searching, this looks like it will be very difficult to get working as desired.
This doesn't directly answer your question, but it may be an option:
Here's the example code:
class TestInputViewController: UIViewController {
let testLabel: InputLabel = InputLabel()
override func viewDidLoad() {
super.viewDidLoad()
let instructionLabel = UILabel()
instructionLabel.textAlignment = .center
instructionLabel.text = "Tap yellow label to edit..."
let centeringFrameView = UIView()
// label properties
let fnt: UIFont = .systemFont(ofSize: 32.0)
testLabel.isUserInteractionEnabled = true
testLabel.font = fnt
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.25
testLabel.numberOfLines = 2
testLabel.setContentHuggingPriority(.required, for: .vertical)
let minLabelHeight = ceil(fnt.lineHeight)
// so we can see the frames
centeringFrameView.backgroundColor = .red
testLabel.backgroundColor = .yellow
[centeringFrameView, instructionLabel, testLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(instructionLabel)
view.addSubview(centeringFrameView)
centeringFrameView.addSubview(testLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// instruction label centered at top
instructionLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
instructionLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// centeringFrameView 20-pts from instructionLabel bottom
centeringFrameView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 20.0),
// Leading / Trailing with 20-pts "padding"
centeringFrameView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
centeringFrameView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// test label centered vertically in centeringFrameView
testLabel.centerYAnchor.constraint(equalTo: centeringFrameView.centerYAnchor, constant: 0.0),
// Leading / Trailing with 20-pts "padding"
testLabel.leadingAnchor.constraint(equalTo: centeringFrameView.leadingAnchor, constant: 20.0),
testLabel.trailingAnchor.constraint(equalTo: centeringFrameView.trailingAnchor, constant: -20.0),
// height will be zero if label has no text,
// so give it a min height of one line
testLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: minLabelHeight),
// centeringFrameView height = 3 * minLabelHeight
centeringFrameView.heightAnchor.constraint(equalToConstant: minLabelHeight * 3.0)
])
// to handle user input
testLabel.editCallBack = { [weak self] str in
guard let self = self else { return }
self.testLabel.text = str
}
testLabel.doneCallBack = { [weak self] in
guard let self = self else { return }
// do something when user taps done / enter
}
let t = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped(_:)))
testLabel.addGestureRecognizer(t)
}
#objc func labelTapped(_ g: UITapGestureRecognizer) -> Void {
testLabel.becomeFirstResponder()
testLabel.inputContainerView.theTextView.text = testLabel.text
testLabel.inputContainerView.theTextView.becomeFirstResponder()
}
}
class InputLabel: UILabel {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
lazy var inputContainerView: CustomInputAccessoryView = {
let customInputAccessoryView = CustomInputAccessoryView(frame: .zero)
customInputAccessoryView.backgroundColor = .blue
customInputAccessoryView.editCallBack = { [weak self] str in
guard let self = self else { return }
self.editCallBack?(str)
}
customInputAccessoryView.doneCallBack = { [weak self] in
guard let self = self else { return }
self.resignFirstResponder()
}
return customInputAccessoryView
}()
}
class CustomInputAccessoryView: UIView, UITextViewDelegate {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
let theTextView: UITextView = {
let tv = UITextView()
tv.isScrollEnabled = false
tv.font = .systemFont(ofSize: 16)
tv.autocorrectionType = .no
tv.returnKeyType = .done
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(theTextView)
theTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constraint text view with 8-pts "padding" on all 4 sides
theTextView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
theTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
theTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
theTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
])
theTextView.delegate = self
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
doneCallBack?()
}
return true
}
func textViewDidChange(_ textView: UITextView) {
editCallBack?(textView.text ?? "")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}

In keeping with the recent questions on closures used to pass data between VCs, how would I do the same when using a containerVC within the rootVC?

I saw a recent bountied question (can find the link if you wish to see it) about using closures to pass data between VCs where one VC was embedded in a navigation controller. While the use of a closure there was fairly easy since there was a direct point of contact between the two VCs (in the form a segue), I have been wondering how the same would work if this was not the case.
As an example, consider the following set up (similar to the OG question that inspired this post):
RootVC, which has a counter UILabel
A subContainer VC which takes up the lower half of RootVC, which has a button, pressing which should increment the UILabel on RootVC by one.
I have prepared the code as follows (with some code taken from the OG question):
RootVC:
class RootVC: UIViewController {
var tappedCount: Int = 0
let pagingContainer: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var label: UILabel = {
let label = UILabel()
label.text = "\(tappedCount)"
label.textAlignment = .center
label.font = UIFont(name: "Copperplate", size: 90)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(label)
view.addSubview(pagingContainer)
pagingContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
pagingContainer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
pagingContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
pagingContainer.heightAnchor.constraint(equalToConstant: 500).isActive = true
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
addChild(pageController)
pageController.didMove(toParent: self)
pageController.view.translatesAutoresizingMaskIntoConstraints = false
pagingContainer.addSubview(pageController.view)
pageController.view.heightAnchor.constraint(equalTo: pagingContainer.heightAnchor, multiplier: 1).isActive = true
pageController.view.widthAnchor.constraint(equalTo: pagingContainer.widthAnchor, multiplier: 1).isActive = true
pageController.view.topAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true
pageController.view.bottomAnchor.constraint(equalTo: pagingContainer.bottomAnchor).isActive = true
pageController.view.leadingAnchor.constraint(equalTo: pagingContainer.leadingAnchor).isActive = true
pageController.view.trailingAnchor.constraint(equalTo: pagingContainer.trailingAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: pagingContainer.topAnchor).isActive = true
}
}
SubContainerVC:
class SubContainerVC: UIViewController {
var callback : (() -> Void)?
let button: UIButton = {
let button = UIButton()
button.setTitle("Button!", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.backgroundColor = .green
return button
}()
#objc func buttonPressed(_ sender: UIButton) {
print("Hello")
//Pressing this button should increment the label on RootVC by one.
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
And the PageViewController swift file:
class PageViewController: UIPageViewController {
lazy var subViewControllers:[UIViewController] = {
return [SubContainerVC()]
}()
init(transitionStyle style:
UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [String : Any]? = nil) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
setViewControllerFromIndex(index: 0)
}
func setViewControllerFromIndex(index:Int) {
self.setViewControllers([subViewControllers[index]], direction: UIPageViewController.NavigationDirection.forward, animated: true, completion: nil)
}
}
extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
if currentIndex <= 0 {
return nil
}
return subViewControllers[currentIndex-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
if currentIndex >= subViewControllers.count-1 {
return nil
}
return subViewControllers[currentIndex+1]
}
}
You can inject the closure downstream to SubContainerVC, this will result in the closure execution coming up upstream.
Something along the lines (kept only the relevant VC code):
class SubContainerVC {
var buttonCallback: () -> Void = { }
#objc func buttonPressed(_ sender: UIButton) {
print("Hello")
buttonCallback()
}
}
class PageViewController: UIViewController {
// Note that you don't need the extra closure call for lazy vars
lazy var subViewControllers = [SubContainerVC()] {
didSet {
// just in case the controllers might change later on
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
}
}
var buttonCallback: () -> Void = { } {
didSet {
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
}
}
}
class RootVC: UIViewController {
var tappedCount: Int = 0 {
didSet {
label.text = "\(tappedCount)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
// this will trigger the `didSet` from PageViewController, resulting
// in the callback being propagated downstream
pageController.buttonCallback = { self.tappedCount += 1 }
}
}

UITextField not working with add constraints programmatically

Here is my code , I am adding UITextfield programtically in scrollview. But UITextField is unable to open keyboard.
It looks like UITextField is not enabling even added user interaction enabled true.
I only use the constraints, no storyboard, no xibs. Only through Constraints Programmatically.
Below is my code :
class SignupViewController : UIViewController {
var backButton : UIButton!
var titleLabel : UILabel!
var navBarView : UIView!
var scrollView : UIScrollView!
var scrollMainView : UIView!
var emailfieldView : UIView!
var emailTextField : UITextField = UITextField() override func viewDidLoad() {
super.viewDidLoad()
setDesign()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}func setDesign(){
setNavegationBar()
setBackgroundImage()
addScrollView()
}
func setBackgroundImage(){
let backgroundImage = UIImageView(image: UIImage(named: "loginbg.png"))
self.view.addSubview(backgroundImage)
backgroundImage.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = backgroundImage.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0)
let trailingConst = backgroundImage.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = backgroundImage.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0)
let bottomConst = backgroundImage.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([leadingConst,trailingConst,topConst,bottomConst])
}
func setNavegationBar(){
navigationItem.title = "Join"
view.backgroundColor = UIColor.white
navigationController?.navigationBar.isHidden = true
navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
setNavBarView()
}
func setNavBarView(){
navBarView = UIView()
self.view.addSubview(navBarView)
navBarView.translatesAutoresizingMaskIntoConstraints = false
let guide = view.safeAreaLayoutGuide
let heightCost = navBarView.heightAnchor.constraint(equalToConstant: 64.0)
let leadingCost = navBarView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0)
let trailingConst = navBarView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topCost = navBarView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0.0)
NSLayoutConstraint.activate([trailingConst,heightCost,topCost,leadingCost])
setBackButton()
setNavTitle()
}
func setBackButton(){
backButton = UIButton(type: UIButtonType.custom)
backButton.setImage(UIImage(named: "join_back"), for: UIControlState.normal)
navBarView.addSubview(backButton)
backButton.translatesAutoresizingMaskIntoConstraints = false
let widthCost = backButton.widthAnchor.constraint(equalToConstant: 44.0)
let heightCost = backButton.heightAnchor.constraint(equalToConstant: 44.0)
let leadingCost = backButton.leadingAnchor.constraint(equalTo: navBarView.leadingAnchor, constant: 0.0)
let topCost = backButton.topAnchor.constraint(equalTo: navBarView.topAnchor, constant: 0.0)
NSLayoutConstraint.activate([widthCost,heightCost,topCost,leadingCost])
backButton.addTarget(self, action: #selector(self.backButtonPress), for: UIControlEvents.touchUpInside)
}
func setNavTitle(){
titleLabel = UILabel()
titleLabel.text = "Join Dubai Store"
titleLabel.font = UIFont(name: "Dubai-Regular", size: 22.0)
titleLabel.textAlignment = NSTextAlignment.center
titleLabel.tintColor = UIColor(hexString: "#353535")
navBarView.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
let heightCost = titleLabel.heightAnchor.constraint(equalToConstant: 44.0)
let topCost = titleLabel.topAnchor.constraint(equalTo: navBarView.topAnchor, constant: 0.0)
let centerCost = titleLabel.centerXAnchor.constraint(equalTo: navBarView.centerXAnchor)
NSLayoutConstraint.activate([heightCost,topCost,centerCost])
}
#objc func backButtonPress(){
self.view.endEditing(true)
self.dismissView()
}
func addScrollView(){
scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.layer.borderWidth = 1.0
scrollView.layer.borderColor = UIColor.red.cgColor
scrollView.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0)
let trailingConst = scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = scrollView.topAnchor.constraint(equalTo: navBarView.bottomAnchor, constant: 0)
let bottomConst = scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([leadingConst,trailingConst,topConst,bottomConst])
addScrollMainView()
}
func addScrollMainView() {
scrollMainView = UIView()
scrollView.addSubview(scrollMainView)
scrollMainView.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = scrollMainView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0)
let trailingConst = scrollMainView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = scrollMainView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0)
let bottomConst = scrollMainView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([topConst,leadingConst,trailingConst,bottomConst])
emailFieldView()
}
func emailFieldView(){
emailfieldView = UIView()
emailfieldView.isUserInteractionEnabled = true
emailfieldView.translatesAutoresizingMaskIntoConstraints = false
scrollMainView.addSubview(emailfieldView)
let topCost = emailfieldView.topAnchor.constraint(equalTo: scrollMainView.topAnchor, constant: 0.0)
let leadingConst = emailfieldView.leadingAnchor.constraint(equalTo: scrollMainView.leadingAnchor, constant: 0)
let trailingConst = emailfieldView.trailingAnchor.constraint(equalTo: scrollMainView.trailingAnchor, constant: 0)
let heightCost = emailfieldView.heightAnchor.constraint(equalToConstant: 62.0)
NSLayoutConstraint.activate([trailingConst,heightCost,topCost,leadingConst])
//emailTextField = UITextField(frame: CGRect(x: 10, y: 0, width: SCREEN_WIDTH, height: 50))
emailTextField.placeholder = "Email"
emailTextField.layer.borderColor = UIColor.red.cgColor
emailTextField.layer.borderWidth = 1.0
// emailTextField.font = UIFont.systemFont(ofSize: 15)
// emailTextField.borderStyle = UITextBorderStyle.none
// emailTextField.keyboardType = UIKeyboardType.default
// emailTextField.returnKeyType = UIReturnKeyType.done
// //emailTextField.clearButtonMode = UITextFieldViewMode.whileEditing
emailTextField.isUserInteractionEnabled = true
emailTextField.allowsEditingTextAttributes = true
//emailTextField.contentVerticalAlignment = UIControlContentVerticalAlignment.center
emailTextField.addTarget(self, action: #selector(self.textFieldShouldBeginEditing), for: UIControlEvents.touchUpInside)
emailfieldView.addSubview(emailTextField)
emailTextField.translatesAutoresizingMaskIntoConstraints = false
emailTextField.contentMode = UIViewContentMode.left
emailTextField.delegate = self
let etopCost = emailTextField.topAnchor.constraint(equalTo: emailfieldView.topAnchor, constant: 0.0)
let eleadingConst = emailTextField.leadingAnchor.constraint(equalTo: emailfieldView.leadingAnchor, constant: 10)
let etrailingConst = emailTextField.trailingAnchor.constraint(equalTo: emailfieldView.trailingAnchor, constant: -10)
let eheightCost = emailTextField.heightAnchor.constraint(equalToConstant: 50.0)
NSLayoutConstraint.activate([etopCost,eleadingConst,etrailingConst,eheightCost])
self.scrollMainView.bringSubview(toFront: emailTextField)
emailTextField.isAccessibilityElement = true
} }
extension SignupViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
// return NO to disallow editing.
print("TextField should begin editing method called")
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
// became first responder
print("TextField did begin editing method called")
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
// return YES to allow editing to stop and to resign first responder status. NO to disallow the editing session to end
print("TextField should snd editing method called")
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
// may be called if forced even if shouldEndEditing returns NO (e.g. view removed from window) or endEditing:YES called
print("TextField did end editing method called")
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
// if implemented, called in place of textFieldDidEndEditing:
print("TextField did end editing with reason method called")
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// return NO to not change text
print("While entering the characters this method gets called")
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
// called when clear button pressed. return NO to ignore (no notifications)
print("TextField should clear method called")
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// called when 'return' key pressed. return NO to ignore.
print("TextField should return method called")
// may be useful: textField.resignFirstResponder()
return true
}
}
I just run your code, it looks like scrollMainView is not visible in the views' hierarchy. Just change the constraints. Here is the code:
func addScrollMainView() {
scrollMainView = UIView()
scrollView.addSubview(scrollMainView)
scrollMainView.translatesAutoresizingMaskIntoConstraints = false
let leadingConst = scrollMainView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0)
let trailingConst = scrollMainView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0)
let topConst = scrollMainView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0)
let bottomConst = scrollMainView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([topConst,leadingConst,trailingConst,bottomConst])
emailFieldView()
}
BTW, It is not related to your question, but it is better to create bg at the beginning. Just use the code below:
func setDesign(){
setBackgroundImage()
setNavegationBar()
addScrollView()
}

UIVisualEffectView creating unwanted shadow while presenting new view

In my custom presentation transition I've created a new view controller which will pre presented on top of the current active view controller (see screenshot). Somehow there's a shadow behind the blue view controller and I have no idea where it's coming from. Is there a way to stop getting that shadow?
The project is completely empty and has only 2 empty view controllers.
This is the code I'm using:
class ViewController: UIViewController {
let transitionDelegate = TransitionManager()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellowColor()
let button = UIButton(type: .System)
button.frame = CGRectMake(10, 10, 50, 50)
button.addTarget(self, action: "test:", forControlEvents: .TouchUpInside)
button.backgroundColor = UIColor.redColor()
view.addSubview(button)
}
func test(sender: UIButton) {
let destination = UIViewController()
destination.view.backgroundColor = .blueColor()
destination.transitioningDelegate = transitionDelegate
destination.modalPresentationStyle = .Custom
presentViewController(destination, animated: true, completion: nil)
}
}
The code for presenting the view:
class PresentingTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.3
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let presented = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let container = transitionContext.containerView()!
let durations = transitionDuration(transitionContext)
presented.view.alpha = 0
container.addSubview(presented.view)
UIView.animateWithDuration(durations, animations: { presented.view.alpha = 1 }) { transitionContext.completeTransition($0) }
}
}
The code for handling the presenting view controller:
class PresentationController: UIPresentationController {
var background: UIView!
override init(presentedViewController: UIViewController, presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController)
prepareBackground()
}
func prepareBackground() {
self.background = UIView(frame: presentingViewController.view.bounds)
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
blur.frame = background.bounds
blur.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
background.addSubview(blur)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "backgroundTapped:")
background.addGestureRecognizer(tapRecognizer)
}
func backgroundTapped(tapRecognizer: UITapGestureRecognizer) {
presentingViewController.dismissViewControllerAnimated(true, completion: nil)
}
override func presentationTransitionWillBegin() {
let container = containerView!
background.frame = container.bounds
background.alpha = 0.0
container.insertSubview(background, atIndex: 0)
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 1.0 }, completion: nil)
}
override func dismissalTransitionWillBegin() {
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({ _ in self.background.alpha = 0.0 }, completion: nil)
}
override func frameOfPresentedViewInContainerView() -> CGRect {
return containerView!.bounds.insetBy(dx: 100, dy: 100)
}
override func containerViewWillLayoutSubviews() {
background.frame = containerView!.bounds
presentedView()!.frame = frameOfPresentedViewInContainerView()
}
}