change text of UILabel with timer - ios

i want to change the text of a UILabel after a certain amount of time. but the text isn't changed after the set amount of time. how do i fix this?
see my code:
var countDownText = "hello"
override func didMoveToView(view: SKView) {
startButton = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 90))
startButton.text = "\(countDownText)"
startButton.center = CGPointMake(view.frame.size.width / 2, view.frame.size.height/2)
startButton.textColor = UIColor.darkGrayColor()
startButton.font = UIFont(name: "Arial", size: 20)
startButton.textAlignment = NSTextAlignment.Center
self.view?.addSubview(startButton)
countDownTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("countDownFunc"), userInfo: nil, repeats: true)
}
func countDownFunc(){
theTime++
if(theTime >= 4){
countDownText = "testText"
}
if(theTime >= 8){
spawnEnemys()
startButton.removeFromSuperview()
countDownTimer.invalidate()
}
print(theTime)
}
thanks in advance for all your help :D

Your countDownFunc should be:
func countDownFunc(){
theTime++
if(theTime >= 4){
countDownText = "testText"
startButton.text = countDownText
}
if(theTime >= 8){
spawnEnemys()
startButton.removeFromSuperview()
countDownTimer.invalidate()
}
print(theTime)
}

There's a design flaw in your code.
You change the string assigned to the view controller's countDownText property, but that does not also change the label's current text.
Here's a simple playground example to illustrate the problem:
import UIKit
var str = "Hello, playground"
var label = UILabel()
label.text = str
str = "Goodbye, playground"
print(label.text) // "Hello, playground"
If you also want to update the label's text, you need to update its text property, similar to what you initially did:
startButton.text = "\(countDownText)"
This will update the label's text to match the countDownText property's new value.

Related

How to know which line of a TextView the user has tapped on

I'm building an app that has a multi-line text editor (UITextView in UIKit, and TextEditor in SwiftUI). When the user tap on any line of the multi-line text, I should know which line is that, so I can print that line or do any other task.
Is there a way to know which line of a UITextView the user has tapped on (using textViewDidChangeSelection delegate function or anything else)?
I was thinking of storing the whole text in an Array (each line is an element), and updating the array continuously, but the problem still exists, how shall I know which line has been tapped on to update the array accordingly?
Note: I'm using Attributed Strings to give every line different styling.
Swift 5 UIKit Solution:
Try to add tap gesture to textView and detect the word tapped:
let textView: UITextView = {
let tv = UITextView()
tv.text = "ladòghjòdfghjdaòghjjahdfghaljdfhgjadhgf ladjhgf dagf adjhgf adgljdgadsjhladjghl dgfjhdjgh jdahgfljhadlghal dkgjafahd fgjdsfgh adh"
tv.textColor = .black
tv.font = .systemFont(ofSize: 16, weight: .semibold)
tv.textContainerInset = UIEdgeInsets(top: 40, left: 0, bottom: 0, right: 0)
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
After that in viewDidLoad set tap gesture and constraints:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapResponse(recognizer:)))
textView.addGestureRecognizer(tap)
view.addSubview(textView)
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
now call the function to detect word:
#objc func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: textView)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
guard let position2 = textView.closestPosition(to: position) else { return }
let tapPosition: UITextPosition = position2
guard let textRange: UITextRange = textView.tokenizer.rangeEnclosingPosition(tapPosition, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1)) else {return}
let tappedWord: String = textView.text(in: textRange) ?? ""
print("tapped word:", tappedWord)
}
with Attributed Strings it is the same thing.
UPDATE
Add this function to detect line:
#objc func didTapTextView(recognizer: UITapGestureRecognizer) {
if recognizer.state == .recognized {
let location = recognizer.location(ofTouch: 0, in: textView)
if location.y >= 0 && location.y <= textView.contentSize.height {
guard let font = textView.font else {
return
}
let line = Int((location.y - textView.textContainerInset.top) / font.lineHeight) + 1
print("Line is \(line)")
}
}
}
don't forget to change called function on tap:
let tap = UITapGestureRecognizer(target: self, action: #selector(didTapTextView(recognizer:)))
EDIT TO SHOW CURSOR AND SET IT POSITION ON TAP
to show the cursor on tap location add ended state to recognizer in didTapTextView function set text view is editable and become first responder, this is your didTapTextView function look like:
#objc func didTapTextView(recognizer: UITapGestureRecognizer) {
if recognizer.state == .ended {
textView.isEditable = true
textView.becomeFirstResponder()
let location = recognizer.location(in: textView)
if let position = textView.closestPosition(to: location) {
let uiTextRange = textView.textRange(from: position, to: position)
if let start = uiTextRange?.start, let end = uiTextRange?.end {
let loc = textView.offset(from: textView.beginningOfDocument, to: position)
let length = textView.offset(from: start, to: end)
textView.selectedRange = NSMakeRange(loc, length)
}
}
}
if recognizer.state == .recognized {
let location = recognizer.location(ofTouch: 0, in: textView)
if location.y >= 0 && location.y <= textView.contentSize.height {
guard let font = textView.font else {
return
}
let line = Int((location.y - textView.textContainerInset.top) / font.lineHeight) + 1
print("Line is \(line)")
}
}
}
in my example I set cursor color to green to make it much visible, to do it set textView tint color (I added on TextView attributed text):
let textView: UITextView = {
let tv = UITextView()
tv.textContainerInset = UIEdgeInsets(top: 40, left: 0, bottom: 0, right: 0)
tv.translatesAutoresizingMaskIntoConstraints = false
tv.tintColor = .green // cursor color
let attributedString = NSMutableAttributedString(string: "ladòghjòdfghjdaòghjjahdfghaljdfhgjadhgf ladjhgf dagf adjhgf adgljdgadsjhladjghl", attributes: [.font: UIFont.systemFont(ofSize: 20, weight: .regular), .foregroundColor: UIColor.red])
attributedString.append(NSAttributedString(string: " dgfjhdjgh jdahgfljhadlghal dkgjafahd fgjdsfgh adh jsfgjskbfgfs gsfjgbjasfg ajshg kjshafgjhsakhg shf", attributes: [.font: UIFont.systemFont(ofSize: 20, weight: .bold), .foregroundColor: UIColor.black]))
tv.attributedText = attributedString
return tv
}()
This is te result:
I haven't tested out this to the full extent, but here is what you can do: having the selected character range, you can get the generated glyph range like so:
let glyphRange = textView.layoutManager.glyphRange(forCharacterRange: selectedRange,
actualCharacterRange: nil)
Now you have a glyph range, and can use textView.layoutManager.enumerateLineFragments method to find the line fragment (displayed in text view), corresponding to your glyph range. After this, all that remains is to convert the found glyph range for line fragment to character range using NSLayoutManager's method characterRange(forGlyphRange, actualGlyphRange:nil)
This seems to do the trick, although you might need to make adjustments to fit this to your situation (I've tested this on plain text w/o any attributes applied).

Get each line of text in a UILabel

I'm trying to add each line in a UILabel to an array, but the code I'm using doesn't appear to be terribly accurate.
func getLinesArrayOfStringInLabel(label:UILabel) -> [String] {
guard let text: NSString = label.text as? NSString else { return [] }
let font:UIFont = label.font
let rect:CGRect = label.frame
let myFont: CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
attStr.addAttribute(NSAttributedStringKey.font, value:myFont, range: NSMakeRange(0, attStr.length))
let frameSetter:CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path: CGMutablePath = CGMutablePath()
path.addRect(CGRect(x: 0, y: 0, width: rect.size.width, height: 100000))
let frame:CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
let lines = CTFrameGetLines(frame) as NSArray
var linesArray = [String]()
for line in lines {
let lineRange = CTLineGetStringRange(line as! CTLine)
let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
let lineString = text.substring(with: range)
linesArray.append(lineString as String)
}
return linesArray
}
let label = UILabel()
label.numberOfLines = 0
label.frame = CGRect(x: 40, y: 237, width: 265, height: 53)
label.font = UIFont.systemFont(ofSize: 22, weight: UIFont.Weight.regular)
label.text = "Hey there how's it going today?"
label.backgroundColor = .red
bg.addSubview(label)
print(getLinesArrayOfStringInLabel(label: label))
This prints
["Hey there how\'s it going ", "today?"]
But the label looks like this:
I expected to get ["Hey there how\'s it ", "going today?"]. What's going on?
So it appears to be something with UILabel and not something wrong with the function you are using. It was my suspicion that a CATextLayer would render the lines how they are returned from that method and I found out sadly :( that I am right.
Here is a picture of my results:
The red is the exact code you used to create your UILabel.
The green is a CATextLayer with all of the same characteristics of the UILabel from above including font, fontsize, and frame size.
The yellow is a subclassed UIView that is replacing its own layer and returning a CATextLayer. I am attaching it below. You can continue to build it out to meet your needs but I think this is the real solution and the only one that will have the get lines matching the visible lines the user sees. If you come up with a better solution please let me know.
import UIKit
class AGLabel: UIView {
var alignment : String = kCAAlignmentLeft{
didSet{
configureText()
}
}
var font : UIFont = UIFont.systemFont(ofSize: 16){
didSet{
configureText()
}
}
var fontSize : CGFloat = 16.0{
didSet{
configureText()
}
}
var textColor : UIColor = UIColor.black{
didSet{
configureText()
}
}
var text : String = ""{
didSet{
configureText()
}
}
override class var layerClass: AnyClass {
get {
return CATextLayer.self
}
}
func configureText(){
if let textLayer = self.layer as? CATextLayer{
textLayer.foregroundColor = textColor.cgColor
textLayer.font = font
textLayer.fontSize = fontSize
textLayer.string = text
textLayer.contentsScale = UIScreen.main.scale
textLayer.contentsGravity = kCAGravityCenter
textLayer.isWrapped = true
}
}
}
You should also check out Core-Text-Label on GitHub. It renders exactly as the CATextLayers do and would match the return of the get lines. It won't work for my particular needs as I need mine to be resizable and it crashes but if resizing is not need then I would check it out.
Finally I am back again and it appears that it could be a problem of word wrap that was started in iOS 11 where they do not leave an orphan word on a line.

How to use variables in CGRectMake function - attempting to output data in table-like structure

I am attempting to output a list of an account's 'friends' by pragmatically creating and stacking labels. I have fetched the accounts' friends from a MySQL database (accessible via NSURL interacting with a PHP script) and loaded them into an array. The goal is to loop through the array and create a label that is positioned slightly below the previous one each iteration. The problem I have run into is when I pass a variable into the CGRectMake function to create the label, it will not accept the int because it is not of type "CGFloat". I feel like I have tried just about everything, but I can never cast the data type correctly.Here is my code:
import UIKit
class viewFriendsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let friendsArray = ["billy", "joe", "bob", "jim"]
var inc = 0
for index in friendsArray {
let label: UILabel = UILabel()
let yPos: CGFloat = Float(inc * 10)
label.frame = CGRectMake(50, 50, 200, yPos)
label.backgroundColor = UIColor.blackColor()
label.textColor = UIColor.whiteColor()
label.textAlignment = NSTextAlignment.Center
label.text = "\(index)"
self.view.addSubview(label)
inc = inc + 1 // I have not been able to play with the label yet to determine how to move it's vertical position because I cannot run the program due to the casting error.
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
try this
var inc = 0
for index in friendsArray {
let label: UILabel = UILabel()
//type conversion is
let yPos = CGFloat(inc * 10)
label.frame = CGRectMake(50,yPos,200,50) // if you want to change the Y-Coodinates use in Y frame not in height
label.backgroundColor = UIColor.blackColor()
label.textColor = UIColor.whiteColor()
label.textAlignment = NSTextAlignment.Center
label.text = "\(index)"
self.view.addSubview(label)
inc = inc + 1
}
the above choice is not work try this
var inc : CGFloat = 0
for index in friendsArray {
let label: UILabel = UILabel()
label.frame = CGRectMake(50,inc,200,50) // if you want to change the Y-Coodinates use in Y frame not in height
label.backgroundColor = UIColor.blackColor()
label.textColor = UIColor.whiteColor()
label.textAlignment = NSTextAlignment.Center
label.text = "\(index)"
self.view.addSubview(label)
inc = inc * 10 // inc + 10
}
You should take advantage of the new notation for CGRect in swift instead of using legacy c macros, these allow for usage with Int, Double and Float:
CGRect(x: 50, y: 200, width: 20, height: 40)
Note that these frames are now created using standard swift init functions for structs.
I've tested it with CGFloat() instead of Float() and it works:
var inc = 0
var ypos: CGFloat = CGFloat(inc * 10)
var rect: CGRect = CGRectMake(50, ypos, 200, 200)
Also note you are currently using your ypos at the position of the height. Remember it is
CGRectMake(<xPos>, <yPos>, <width>, <height>)

How to add and delete textfields on a view when click on add and delete buttons in Swift?

I am able to add a textfield on button click but i am unable to delete the textfields that i have added, i am only able to delete the recent textfield that i have added but unable to delete the other previous textfields, how can I delete/remove those textfields in Swift.
I have successfully added the buttons but i am unable to delete those textfields.
I have tried to get those tag values for each textfield but here I am missing some logic please help in this regard. Thanks in advance.
Below is the image for reference when i click on delete(-) button then with respect to that textfield must be removed not all the subviews.
// for add method
#IBAction func btnAddOptionAction(sender: UIButton)
{
txtOption = UITextField(frame: CGRectMake(52, yFrame+17, 195, 30))
txtOption.borderStyle = .None
txtOption.tag = index
let modifiedURLString = NSString(format:"Option %d", index) as String
//textField.text = modifiedURLString
txtOption.attributedPlaceholder = NSAttributedString(string:modifiedURLString,
attributes:[NSForegroundColorAttributeName: UIColor.whiteColor()])
txtOption.font = UIFont(name: "Avenir Medium", size: 16)
txtOption.textColor = UIColor.whiteColor()
self.scrOptions.addSubview(txtOption)
btnDeleteOption.frame = CGRectMake(259, txtOption.frame.origin.y + 8, 25, 25)
btnAddOption.frame = CGRectMake(259, txtOption.frame.origin.y + txtOption.frame.size.height + 10, 25, 25)
btnDeleteOption.hidden = false
print("Add index", index)
index++
yFrame = yFrame + 40
scrOptions.contentSize = CGSizeMake(0, btnAddOption.frame.origin.y + btnAddOption.frame.size.height + 20)
}
//For delete method
#IBAction func btnDeleteOptionAction(sender: UIButton)
{
yFrame = yFrame - 40
index--
sender.tag = index
txtOption.removeFromSuperview()
imgoption.removeFromSuperview()
imgUnderLine.removeFromSuperview()
btnDeleteOption.frame = CGRectMake(259, yFrame - 15, 25, 25)
btnAddOption.frame = CGRectMake(259, yFrame + 32 - 15, 25, 25)
if index <= 1
{
btnDeleteOption.frame = CGRectMake(259, 25, 25, 25)
btnAddOption.frame = CGRectMake(259, 25, 25, 25)
btnDeleteOption.hidden = false
}
print("y-axis ",btnDeleteOption.frame.origin.y)
}
What about:
var myTag = 0
if let myView = UIView() {
view.addSubview(myView)
myView.tag = myTag++
}
if let viewWithTag = self.view.viewWithTag(myTag) {
viewWithTag.removeFromSuperview()
}
It must remove the last element
Try removing all subviews.
After removing all subviews add required textfield in the view.
Hope it helps.. Happy Coding.. :)
#IBOutlet weak var updateIdView: UIView!
let textField: UITextField = updateIdView.viewWithTag(textFieldTag) as! UITextField
textField.removeFromSuperview()

UITextField beginningOfDocument property returning nil

I have created a UITextField programatically. Now when I try to get its beginningOfDocument property, it is becoming nil.
Code is as follows:
public func addTextField(title:String?=nil) -> UITextField {
kWindowHeight -= 80.0
let txt: UITextField = UITextField(frame: CGRect(x: 13, y: 67, width: 213.00, height: 35));
txt.addTarget(self, action: "textFieldDidChange", forControlEvents: UIControlEvents.EditingChanged)
txt.borderStyle = UITextBorderStyle.RoundedRect
txt.layer.cornerRadius = 3
txt.layer.borderColor = UIColorFromRGB(0x1E8ADB).CGColor
txt.font = UIFont(name: kDefaultFont, size: 14)
txt.autocapitalizationType = UITextAutocapitalizationType.Words
txt.clearButtonMode = UITextFieldViewMode.WhileEditing
txt.layer.masksToBounds = true
txt.layer.borderWidth = 1.0
if title != nil {
txt.placeholder = title!
}
contentView.addSubview(txt)
return txt
}
In some other class I am calling:
let textField = AlertView().addTextField("Send SMS")
var begining = textField.beginingOfDocument
If you merely create an UITextField then immediately try to access beginningOfDocument property OR even after trying to add it to a superview, you will always get an uninitialized value.
Try to retrieve that property after the UITextField becomes the first responder and see the result :-)
Good luck.

Resources