Edit a UITextView in a ScrollView swift - swift

I've been looking for an answer for a while to this basic problem but can't find anything to solve it.
I have a UITextView in a UIScrollVIew, but I can't edit the text of my textView, it means when I run the project and click on the textView the keyboard does not show up and I can't edit the text.
I tried different configuration but it seems that none works.
My code :
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
var imageView2: UIImageView!
var scrollView: UIScrollView!
var textView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
imageView2 = UIImageView(image: UIImage(named: "image.png"))
scrollView = UIScrollView(frame: view.bounds)
scrollView.contentSize = imageView2.bounds.size
scrollView.isUserInteractionEnabled = true
scrollView.isExclusiveTouch = true
scrollView.canCancelContentTouches = true
self.scrollView.delegate = self
view.addSubview(scrollView)
scrollView.addSubview(imageView2)
textView = UITextView(frame: CGRect(x:24,y: 100,width: 340,height: 290))
textView.backgroundColor = UIColor(red: 0.00, green: 1.00, blue: 0.00, alpha: 1.00)
textView.text = "bla bla bla"
imageView2.addSubview(textView)
I will probably be ashamed when I will get the answer.. I guess it's something easy but can't seem to find it. I enabled the user to interact, I thought it would be enough, which is not.
Thanks

you need to add textview in imageview before adding it into scroll view.

Related

Is there a way to make a completely transparent UIButton that works?

I've a custom UINavigationItem title view. It has a label pushed to the top of it, which I've enabled to respond to .touchDown and send an action.
However, the taps don't register near the top of the label, presumably because the active region is clipped. So I configured another invisible view (not in navigation item), and set it up as a control, and positioned it above that navigation title view label.
However, it doesn't work unless I set the 'invisible' view's alpha to at least 0.02, because Apple seems to intentionally disable action for a view with an alpha less than that. Unfortunately, against a black screen in dark mode, the 'invisible' hitpad view shows up as a slightly grey rectangle, which is not a good aesthetic.
I guess I could go to some lengths to try to make the button background color match the screen background at that location, but it seems a bit tacky.
What alternatives might I have?
You can simply create a blank png image, and add it in top of your title view. make sure to set the imageView and the title view isUserInteractionEnabled properties to true:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(
target: self,
action: #selector(tap)
)
imageView.addGestureRecognizer(tap)
imageView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
imageView.image = UIImage(named: "blank")
let titleLabel = UILabel()
titleLabel.text = "Transparent Button"
titleLabel.autoresizingMask = [.flexibleHeight, .flexibleWidth]
titleLabel.addSubview(imageView)
navigationItem.titleView = titleLabel
navigationItem.titleView?.isUserInteractionEnabled = true
}
#objc func tap(_ gesture: UITapGestureRecognizer) {
print(#function)
}
}
Sample project
You can also just add your gesture recognizer directly to your titleView. No need for the transparent image at all unless you need to control the area of the gesture:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
let tap = UITapGestureRecognizer(
target: self,
action: #selector(tap)
)
let titleLabel = UILabel()
titleLabel.text = "Transparent Button"
titleLabel.autoresizingMask = [.flexibleHeight, .flexibleWidth]
titleLabel.addGestureRecognizer(tap)
navigationItem.titleView = titleLabel
navigationItem.titleView?.isUserInteractionEnabled = true
}
#objc func tap(_ gesture: UITapGestureRecognizer) {
print(#function)
}
}
This is an adaptation of the #LeoDabus' Accepted answer that works. However it was utterly informed by his explanation and example. The only meaningful change I made to Leo's example was to create a real empty image programmatically, and drop the label generation. Without a real empty UIImage(), the only way to make taps on the region work that I found is to set the image view's background color to non-clear.
func emptyImage(with size: CGSize) -> UIImage?
{
UIGraphicsBeginImageContext(size)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func configureButtons() {
let imageView = UIImageView(image: emptyImage(with: CGSize(width: view.frame.size.width - 250, height: 44)))
imageView.frame = CGRect(x: 140, y: self.view.safeAreaInsets.top + 50,
width: view.frame.size.width - 250, height: 44)
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(actionEnableTitleEditing))
imageView.addGestureRecognizer(tap)
imageView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(imageView)
}

Swift UIKIT components do not show on a working view

I'm turning my hand from years of Java / C++ / C to trying to learn Swift and how to develop on Apple.
I've tried a lot of tutorials and for what I want to achieve I need to use a splitview controller.
I need to have a few different detail views and can't work out how to do this via storyboard and thought I'd try to do it with code.
Following a tutorial and several google searches Im hitting a problem which I realise is me being a newbiee and ask for your help.
To keep things simple I created a test viewcontroller class and set it in the storyboard no problem.
import UIKit;
class tesctVC : UIViewController {
var scoreLabel: UILabel!
override func loadView() {
view = UIView()
view.backgroundColor = .white
scoreLabel = UILabel()
scoreLabel.translatesAutoresizingMaskIntoConstraints = false
scoreLabel.textAlignment = .right
scoreLabel.text = "Score: 0"
view.addSubview(scoreLabel)
// more code to come!
}
}
When I change to background color it shows as expected.
But when I try to add anything else, I've tried UILabels, UIButtons etc, they do not show.
Would you please give me some pointers?
Thanks again.
I thought this may help others.
This is still WIP but shows some hints I found along the way.
I want to add a bunch of labels & buttons etc.
This in so far as is written does the job. It needs more formatting and so on.
The extension is needed to set a stacks background color. I think someone explained its because a StackView is not a drawable object.
extension UIStackView {
func addBackground(color: UIColor) {
let subView = UIView(frame: bounds)
subView.backgroundColor = color
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(subView, at: 0)
}
}
class DetailViewController: UIViewController , UIWebViewDelegate {
var usernameEdt: UITextField!
var passwordEdt: UITextField!
var statusLabel: UILabel!
var LogubButton = UIButton()
// Draw the login page
func loginPage() {
// Create the top level statck
let stackView = UIStackView()
stackView.addBackground(color: .white)
stackView.axis = .vertical
stackView.alignment = .center // .leading .firstBaseline .center .trailing .lastBaseline
stackView.distribution = .fillEqually // .fillEqually .fillProportionally .equalSpacing .equalCentering
// Add the title
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.font = UIFont.boldSystemFont(ofSize: 20.0)
label.text = "Login Details"
label.textColor = .blue
stackView.addArrangedSubview(label)
// Add Username
usernameEdt = UITextField()
usernameEdt.placeholder = "Enter Your Username"
stackView.addArrangedSubview(usernameEdt)
// Add Password
passwordEdt = UITextField()
passwordEdt.placeholder = "Enter Your Password"
stackView.addArrangedSubview(passwordEdt)
self.view = stackView
}
Thanks
Jeff

not added label to textField like right view

I'm trying to add a label to the text field but nothing is displayed
#IBOutlet weak var myTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let my = UILabel()
myLabel.text = "text"
myLabel.textColor = .black
myTextField.rightView = myLabel
myTextField.rightViewMode = .always
}
Try to add a frame to the label such as myLabel = UILabel(frame: CGRectMake(0, 0, 50, 21)).
Make sure that the Textfield is behind the Label! Besides that it should be showing properly.
set sizeToFit(), in my case it's help

How can I get a textview to trigger a scrollview to move up for the keyboard?

I'm creating a form that lives on a view within a scrollview. It contains two textFields and one textView. I've cobbled together some code from online resources(like this one) so that it scrolls up if a textField is ever too low on the view/obstructed by the keyboard. The problem is that it doesn't seem to work for textViews.
I've done my best to comprehend why textViews won't trigger the same behavior, but I could really use some help. Here's my VC code:
import UIKit
class AddAPlaceBasicInfoViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
// Outlets
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var categoryTextField: UITextField!
#IBOutlet weak var descriptionTextView: UITextView!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(noti:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(noti:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
// Default to an disabled "Next" button.
nextButton.isEnabled = false
nextButton.backgroundColor = UIColor(red: 89/255, green: 89/255, blue: 89/255, alpha: 1/1)
// Check that fields are filled.
nameTextField.delegate = self
descriptionTextView.delegate = self
// Helper functions.
createToolbar()
descriptionTextViewSetup(descriptionTextView)
}
// Creates the done button above the category picker.
func createToolbar() {
// Creates an instance of UIToolbar() named "toolbar".
let toolbar = UIToolbar()
toolbar.sizeToFit()
// Set up button properties.
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(AddAPlaceBasicInfoViewController.dismissKeyboard))
// Set color to teal.
doneButton.tintColor = UIColor(red: 0/255, green: 134/255, blue: 111/255, alpha: 1)
// Set up button in the toolbar and make it interactive.
toolbar.setItems([doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
// Add the toolbar as an accessory view to the category picker.
nameTextField.inputAccessoryView = toolbar
categoryTextField.inputAccessoryView = toolbar
descriptionTextView.inputAccessoryView = toolbar
// Set toolbar's background to white.
toolbar.backgroundColor = UIColor.white
}
// Function to dismiss the keyboard. Used when the user taps the "Done" button in the toolbar.
func dismissKeyboard() {
view.endEditing(true)
if nameTextField.text?.characters.count == 0 || categoryTextField.text?.characters.count == 0 || descriptionTextView.text == "What's this place like? (You'll be able to add a photo on the next screen)" {
nextButton.isEnabled = false
nextButton.backgroundColor = UIColor(red: 89/255, green: 89/255, blue: 89/255, alpha: 1/1)
} else {
nextButton.isEnabled = true
nextButton.backgroundColor = UIColor(red: 0/255, green: 134/255, blue: 111/255, alpha: 1/1)
}
}
// Function to create placeholder text.
func descriptionTextViewSetup(_ textView: UITextView) {
// Modifications
descriptionTextView.text = "What's this place like? (You'll be able to add a photo on the next screen)"
descriptionTextView.textColor = UIColor(red: 199/255, green: 199/255, blue: 205/255, alpha: 1/1)
descriptionTextView.textContainer.lineFragmentPadding = 0
//descriptionTextView.textColor = UIColor.lightGray
}
//---------------------------------
// MARK: - Notification Center
//---------------------------------
func keyboardWillHide(noti: Notification) {
let contentInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
}
func keyboardWillShow(noti: Notification) {
guard let userInfo = noti.userInfo else { return }
guard var keyboardFrame: CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue else { return }
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
scrollView.contentInset = contentInset
}
}
If you are creating a Form for users where they can enter information then I would suggest using a static UITableView. Static UITableView can be used if your UIViewController is of type UITableViewController. Static TableView makes it very easy to place controls and since UITableView already has a UIScrollView it automatically scrolls when the UITextView is in focus.
Try it out:

iOS8: Auto-layout and Gradient

Setup:
I have a View Controller that consists of a View and a Container View.
The View (Orange) is pinned to top 0, left 0, and right 0.
The Container View (Gray) is pinned to bottom 0, left 0, and right 0.
The View's Bottom Space to: Container View = 0
The View's Proportional Height to Container View = 1
Desired Results:
I would like to add gradient to the background of the View (Orange)
Tried:
I'm using Auto-layout with class sizes to get different behavior on different screen.
Code:
class ViewController: UIViewController {
#IBOutlet weak var graphView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let backgroundColor = CAGradientLayer().graphViewBackgroundColor()
backgroundColor.frame = self.graphView.frame
self.graphView.layer.addSublayer(backgroundColor)
}
I have a category:
extension CAGradientLayer {
func graphViewBackgroundColor() -> CAGradientLayer {
let topColor = UIColor(red: (160/255.0), green: (160/255.0), blue: (160/255.0), alpha: 1)
let bottomColor = UIColor(red: (52/255.0), green: (53/255.0), blue: (52/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
}
}
Result:
As you can see gradient did not cover the entire View.
Question: How can I get the gradient to cover the entire View
Update:
When I place the code in viewDidLayoutSubviews() It looks weird when I rotate:
Simply do it this inside viewDidLayoutSubviews:
override func viewDidLayoutSubview() {
super.viewDidLayoutSubviews
backgroundColor.frame = self.graphView.bounds
}
viewDidLayoutSubviews should be called when you rotate the device.
If it is not called, override this method and do it as,
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
backgroundColor.frame = self.graphView.bounds
}
Try putting your gradient code into viewDidLayoutSubviews instead of viewDidLoad
When viewDidLoad is called the views are not laid out (ie do not have their final frames set yet), so this is why you are only seeing a partial coverage of the gradient