Displaying text one character at a time in swift 2.0 - ios

New to Swift 2.0; trying to learn it, interested in animation and tried to write some code to display a message on screen one character at a time.
I wrote this, it works, but I cannot help but could I not do something with CALayers perhaps and/or alpha values? Or some animate gizmo, something more swift worthy; this feels & looks kinda clunky, sort 1977 really.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let supertext = "A long time ago, in a galaxy far, far away ..."
let round = supertext.characters.count
for i in 0...round {
let delay = Double(i)/10
let subtext = supertext[supertext.startIndex..<supertext.startIndex.advancedBy(i)]
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: "ghostText:", userInfo: ["theText" :subtext], repeats: false)
}
}
var view3:UITextView = UITextView()
func ghostText(timer:NSTimer) {
view3.removeFromSuperview()
let userInfo = timer.userInfo as! Dictionary<String, AnyObject>
let tempText:NSString = (userInfo["theText"] as! NSString)
print(tempText)
let font = UIFont(name: "Georgia", size: 18.0) ?? UIFont.systemFontOfSize(18.0)
let textFont = [NSFontAttributeName:font]
let subChararacter = NSAttributedString(string: String(tempText), attributes: textFont)
view3.frame = CGRect(x: 100, y: 120, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame)-20)
view3.attributedText = subChararacter
self.view.addSubview(view3)
}

I suppose Swiftworthy is subjective, but here's another implementation based on how your code currently works that wraps the NSTimer in a Swift class.
class Timer {
typealias TimerFunction = (Int)->Bool
private var handler: TimerFunction
private var i = 0
init(interval: NSTimeInterval, handler: TimerFunction) {
self.handler = handler
NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerFired:", userInfo: nil, repeats: true)
}
#objc
private func timerFired(timer:NSTimer) {
if !handler(i++) {
timer.invalidate()
}
}
}
class ViewController: UIViewController {
let text: NSAttributedString = {
let font = UIFont(name: "Georgia", size: 18.0) ?? UIFont.systemFontOfSize(18.0)
return NSAttributedString(string: "A long time ago, in a galaxy far, far away ...", attributes: [NSFontAttributeName: font])
}()
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView(frame: CGRect(x: 100, y: 120, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame)-20))
self.view.addSubview(textView)
let _ = Timer(interval: 0.1) {i -> Bool in
textView.attributedText = self.text.attributedSubstringFromRange(NSRange(location: 0, length: i+1))
return i + 1 < self.text.string.characters.count
}
}
}

Related

UIButton works normally, but not in popoverPresentationController

I have a parent container UIView that has three subviews: 1 UITextView, and 2 buttons (the buttons are on top of the UITextView, but contained as subviews of the container UIView). When I present the parent UIView on screen normally, simply inside of a UIViewController, the buttons trigger their associated action methods perfectly and function correctly. However, when I present the UIView inside a view controller that becomes a popover, the view hierarchy shows up properly, but the buttons don't trigger their associated action methods and nothing happens. Is there something about UIPopoverPresentationControllers and buttons that I don't understand?
Adding the buttons to the parent UIView
func layoutButtons(in parent: UIView) {
let speechButton = UIButton(type: .custom)
speechButton.frame = CGRect(x: parent.frame.width - 35, y: parent.frame.height - 35, width: 30, height: 30)
speechButton.setImage(UIImage(named: "sound_icon"), for: .normal)
speechButton.addTarget(self, action: #selector(Textual.textToSpeech), for: UIControlEvents.touchUpInside)
parent.addSubview(speechButton)
let fontSizeButton = UIButton(type: .custom)
fontSizeButton.frame = CGRect(x: textView.frame.width - 75, y: textView.frame.height - 35, width: 30, height: 30)
fontSizeButton.setImage(UIImage(named: "font_size_icon"), for: .normal)
fontSizeButton.addTarget(self, action: #selector(Textual.toggleFontSize), for: UIControlEvents.touchUpInside)
parent.addSubview(fontSizeButton)
// wrap text around the bottom buttons
let excludeSpeechButton = UIBezierPath(rect: speechButton.frame)
let excludeFontSizeButton = UIBezierPath(rect: fontSizeButton.frame)
self.textView.textContainer.exclusionPaths = [excludeSpeechButton, excludeFontSizeButton]
}
#objc func textToSpeech() {
if synth.isPaused {
synth.continueSpeaking()
} else if synth.isSpeaking {
synth.pauseSpeaking(at: .immediate)
} else {
let speechUtterance = AVSpeechUtterance(string: attributedText.string)
speechUtterance.rate = 0.5
synth.speak(speechUtterance)
}
}
#objc func toggleFontSize() {
if self.smallFontSizeMode == true {
self.smallFontSizeMode = false
} else {
self.smallFontSizeMode = true
}
// for each character, multiply the current font size by fontSizeModifier
// http://stackoverflow.com/questions/19386849/looping-through-nsattributedstring-attributes-to-increase-font-size
self.attributedText.beginEditing()
self.attributedText.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, self.attributedText.length), options: NSAttributedString.EnumerationOptions.reverse, using: { (value, range, stop) in
if let oldFont = value as? UIFont {
var newFont: UIFont?
if self.smallFontSizeMode == true { // was big and now toggling to small
newFont = oldFont.withSize(oldFont.pointSize / 2)
} else { // was small and now toggling to big
newFont = oldFont.withSize(oldFont.pointSize * 2)
}
attributedText.removeAttribute(NSFontAttributeName, range: range)
attributedText.addAttribute(NSFontAttributeName, value: newFont!, range: range)
}
})
self.attributedText.endEditing()
self.textView.attributedText = self.attributedText
}
Presenting the UIViewController
func present(viewController: UIViewController, at location: CGRect) {
viewController.modalPresentationStyle = .popover
parentViewController.present(viewController, animated: true, completion: nil)
let popController = viewController.popoverPresentationController
popController?.permittedArrowDirections = .any
popController?.sourceView = parentView
popController?.sourceRect = location
}
UPDATE - I've added popController?.delegate = viewController as the last line of func present(viewController:at:) and below I've added extension UIViewController: UIPopoverPresentationControllerDelegate { }
To answer your question, you will need to tell your presenting view controller who to delegate, so add this below and let me know if it works:
popController?.delegate = viewController

How to pass data from Realm to ViewController

I do news app in swift 2.3, xcode 7.3.1. I have Realm DB and data therein from server. I need move to ViewController when I press button and display selected data(news). The button over by my elements of news. How to pass data from selected elements of news to show details on another ViewController?
This is news elements. The button over by elements of with horizontal scrolling:
This is code:
import UIKit
import RealmSwift
let realm4 = try! Realm()
class MainViewController: UIViewController, HorizontalScrollDelegate {
let newsObj = realm4.objects(News)
let nObj = News()
var imageURL: NSURL?
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let hScroll = HorizontalScroll(frame: CGRectMake(0, 0, 380, 160))
hScroll.delegate = self
hScroll.backgroundColor = UIColor.whiteColor()
self.view.addSubview(hScroll)
view.reloadInputViews()
}
func numberOfScrollViewElements() -> Int {
return newsObj.count
}
func elementAtScrollViewIndex(index: Int) -> UIView {
let indexes = newsObj[index]
let view = UIView(frame: CGRectMake(5.0, 0.0, 200.0, 200.0))
var imageView = UIImageView()
let imageLabel = UIImageView()
let newsLable = UILabel()
let button = UIButton()
var image: UIImage? {
get { return imageView.image }
set {
imageView.image = newValue
imageView.sizeToFit()
}
}
newsLable.text = indexes.newsTitle
imageURL = NSURL(string: indexes.newsImage)
if let url = imageURL {
let imageData = NSData(contentsOfURL: url)
if imageData != nil {
image = UIImage(data: imageData!)
}
}
newsLable.lineBreakMode = .ByWordWrapping
newsLable.font = UIFont(name: "Roboto-Bold", size: 18)
newsLable.textColor = UIColor.whiteColor()
newsLable.frame = CGRectMake(7.0, 90.0, 200.0, 50.0)
newsLable.textAlignment = .Left
newsLable.numberOfLines = 0
button.frame = CGRectMake(0.0, 0.0, 200.0, 200.0)
button.addTarget(indexes, action: #selector(tapAction2), forControlEvents: .TouchUpInside)
print("This is 1 nObj \(self.nObj)")
imageLabel.backgroundColor = UIColor.blackColor()
imageLabel.alpha = 0.45
imageLabel.frame = CGRectMake(5.0, 90.0, 200.0, 65.0)
imageView.frame = view.frame
view.addSubview(imageView)
view.addSubview(imageLabel)
view.addSubview(newsLable)
view.addSubview(button)
return view
}
func tapAction2(index: Int) {
print("This is func INDEX \(index)")
//let segueIndex = index
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let newView = storyBoard.instantiateViewControllerWithIdentifier("NewsDetailViewController") as! NewsDetailViewController
newView.modalPresentationStyle = UIModalPresentationStyle.Custom
print("This is nObj \(self.nObj)")
print("This is INDEX \(index)")
newView.newsOfTitle = String(index)
presentViewController(newView, animated: true, completion: nil)
}
}
I try to pass data with index. But in console I have empty data. Maybe problems with Realm.
In console I see
This is INDEX 140659923446288
But I have not this data in my project
This is my Realm DB
As stated in UIButton Class Reference, the signature of an action method takes one of three forms
#IBAction func doSomething()
#IBAction func doSomething(sender: UIButton)
#IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)
but not func doSomething(index: Int).
That's why you get the wrong index in your method.

How to handle selection when using GMSAutocompleteFetcher in iOS?

I have google place autocomplete which display an GMSAutocompleteFetcher. the code is come from Google developer website but the problem is I can't find a way how to handle selection right after user seeing the words appear on the textView to display it as place ID, so far this is my code:
import UIKit
import GoogleMaps
class FetcherSampleViewController: UIViewController {
var textField: UITextField?
var resultText: UITextView?
var fetcher: GMSAutocompleteFetcher?
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: "handletap:")
self.view.backgroundColor = UIColor.whiteColor()
self.edgesForExtendedLayout = .None
// Set bounds to inner-west Sydney Australia.
let neBoundsCorner = CLLocationCoordinate2D(latitude: -33.843366,
longitude: 151.134002)
let swBoundsCorner = CLLocationCoordinate2D(latitude: -33.875725,
longitude: 151.200349)
let bounds = GMSCoordinateBounds(coordinate: neBoundsCorner,
coordinate: swBoundsCorner)
// Set up the autocomplete filter.
let filter = GMSAutocompleteFilter()
filter.type = .Establishment
// Create the fetcher.
fetcher = GMSAutocompleteFetcher(bounds: bounds, filter: filter)
fetcher?.delegate = self
textField = UITextField(frame: CGRect(x: 5.0, y: 0,
width: self.view.bounds.size.width - 5.0, height: 44.0))
textField?.autoresizingMask = .FlexibleWidth
textField?.addTarget(self, action: "textFieldDidChange:",
forControlEvents: .EditingChanged)
resultText = UITextView(frame: CGRect(x: 0, y: 45.0,
width: self.view.bounds.size.width,
height: self.view.bounds.size.height - 45.0))
resultText?.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
resultText?.text = "No Results"
resultText?.editable = false
resultText?.addGestureRecognizer(tap)
self.view.addSubview(textField!)
self.view.addSubview(resultText!)
}
func textFieldDidChange(textField: UITextField) {
fetcher?.sourceTextHasChanged(textField.text!)
}
func handletap (sender: UITapGestureRecognizer){
print("I dont know what to do here")
}
}
extension FetcherSampleViewController: GMSAutocompleteFetcherDelegate {
func didAutocompleteWithPredictions(predictions: [GMSAutocompletePrediction]) {
let resultsStr = NSMutableAttributedString()
for prediction in predictions {
resultsStr.appendAttributedString(prediction.attributedPrimaryText)
resultsStr.appendAttributedString(NSAttributedString(string: "\n"))
}
resultText?.attributedText = resultsStr
}
func didFailAutocompleteWithError(error: NSError) {
resultText?.text = error.localizedDescription
}
}
I use UITapGestureRecognizer but I don't know what should I do. If you can help me I would appreciate it :)
Since all the predictions are just newline-delimited rows in a text field, it's going to be difficult to tell which of them the user tapped on.
How about instead of a UITextView, you use a UITableView and have one row per prediction. This will make it easy to detect which prediction was selected.

Google place autocomplete in swift doesn't works perfectly

I tried to make "place autocomplete" from google place and I copy paste the codes directly from the website here google developer and I already set the api key too but the problem is why the results look so odd when I typing a place as you can see on the picture it seems works but not perfect. here is my code:
import UIKit
import GoogleMaps
class FetcherSampleViewController: UIViewController {
var textField: UITextField?
var resultText: UITextView?
var fetcher: GMSAutocompleteFetcher?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
self.edgesForExtendedLayout = .None
// Set bounds to inner-west Sydney Australia.
let neBoundsCorner = CLLocationCoordinate2D(latitude: -33.843366,
longitude: 151.134002)
let swBoundsCorner = CLLocationCoordinate2D(latitude: -33.875725,
longitude: 151.200349)
let bounds = GMSCoordinateBounds(coordinate: neBoundsCorner,
coordinate: swBoundsCorner)
// Set up the autocomplete filter.
let filter = GMSAutocompleteFilter()
filter.type = .Establishment
// Create the fetcher.
fetcher = GMSAutocompleteFetcher(bounds: bounds, filter: filter)
fetcher?.delegate = self
textField = UITextField(frame: CGRect(x: 5.0, y: 0,
width: self.view.bounds.size.width - 5.0, height: 44.0))
textField?.autoresizingMask = .FlexibleWidth
textField?.addTarget(self, action: "textFieldDidChange:",
forControlEvents: .EditingChanged)
resultText = UITextView(frame: CGRect(x: 0, y: 45.0,
width: self.view.bounds.size.width,
height: self.view.bounds.size.height - 45.0))
resultText?.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
resultText?.text = "No Results"
resultText?.editable = false
self.view.addSubview(textField!)
self.view.addSubview(resultText!)
}
func textFieldDidChange(textField: UITextField) {
fetcher?.sourceTextHasChanged(textField.text!)
}
}
extension FetcherSampleViewController: GMSAutocompleteFetcherDelegate {
func didAutocompleteWithPredictions(predictions: [GMSAutocompletePrediction]) {
let resultsStr = NSMutableString()
for prediction in predictions {
resultsStr.appendFormat("%#\n", prediction.attributedPrimaryText)
}
resultText?.text = resultsStr as String
}
func didFailAutocompleteWithError(error: NSError) {
resultText?.text = error.localizedDescription
}
}
can anyone help me?
This is because prediction.attributedPrimaryText is an attributed string. Try the following code
func didAutocompleteWithPredictions(predictions: [GMSAutocompletePrediction]) {
let resultsStr = NSMutableAttributedString()
for prediction in predictions {
resultsStr.appendAttributedString(prediction.attributedPrimaryText)
resultsStr.appendAttributedString(NSAttributedString(string: "\n"))
}
resultText?.attributedText = resultsStr
}

iOS Swift displaying character by character from a string array in UILabel.text [duplicate]

New to Swift 2.0; trying to learn it, interested in animation and tried to write some code to display a message on screen one character at a time.
I wrote this, it works, but I cannot help but could I not do something with CALayers perhaps and/or alpha values? Or some animate gizmo, something more swift worthy; this feels & looks kinda clunky, sort 1977 really.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let supertext = "A long time ago, in a galaxy far, far away ..."
let round = supertext.characters.count
for i in 0...round {
let delay = Double(i)/10
let subtext = supertext[supertext.startIndex..<supertext.startIndex.advancedBy(i)]
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: "ghostText:", userInfo: ["theText" :subtext], repeats: false)
}
}
var view3:UITextView = UITextView()
func ghostText(timer:NSTimer) {
view3.removeFromSuperview()
let userInfo = timer.userInfo as! Dictionary<String, AnyObject>
let tempText:NSString = (userInfo["theText"] as! NSString)
print(tempText)
let font = UIFont(name: "Georgia", size: 18.0) ?? UIFont.systemFontOfSize(18.0)
let textFont = [NSFontAttributeName:font]
let subChararacter = NSAttributedString(string: String(tempText), attributes: textFont)
view3.frame = CGRect(x: 100, y: 120, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame)-20)
view3.attributedText = subChararacter
self.view.addSubview(view3)
}
I suppose Swiftworthy is subjective, but here's another implementation based on how your code currently works that wraps the NSTimer in a Swift class.
class Timer {
typealias TimerFunction = (Int)->Bool
private var handler: TimerFunction
private var i = 0
init(interval: NSTimeInterval, handler: TimerFunction) {
self.handler = handler
NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerFired:", userInfo: nil, repeats: true)
}
#objc
private func timerFired(timer:NSTimer) {
if !handler(i++) {
timer.invalidate()
}
}
}
class ViewController: UIViewController {
let text: NSAttributedString = {
let font = UIFont(name: "Georgia", size: 18.0) ?? UIFont.systemFontOfSize(18.0)
return NSAttributedString(string: "A long time ago, in a galaxy far, far away ...", attributes: [NSFontAttributeName: font])
}()
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView(frame: CGRect(x: 100, y: 120, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame)-20))
self.view.addSubview(textView)
let _ = Timer(interval: 0.1) {i -> Bool in
textView.attributedText = self.text.attributedSubstringFromRange(NSRange(location: 0, length: i+1))
return i + 1 < self.text.string.characters.count
}
}
}

Resources