How can I edit the text copied into UIPasteboard - swift

I am trying to manipulate the text the user copied from a UILabel into the UIPasteboard but can't find an example.

Here is a full sample view controller for what you are trying to achieve (read the comments to understand what is going on...):
import UIKit
import MobileCoreServices
class ViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// add an observer (self) for whenever the pasteboard contents change
// this is going to be called whenever the user copies text for example
NSNotificationCenter
.defaultCenter()
.addObserver(
self,
selector: "pasteboardChanged:",
name: UIPasteboardChangedNotification,
object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// make sure to pair addition/removal of observers
// we add the observer in viewWillAppear:, so let's
// remove it in viewWillDisappear: (here)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
// this will be called whenever the pasteboard changes
// we specified this function in our observer registration above
#objc private func pasteboardChanged(notification: NSNotification){
// this is what the user originally copied into the pasteboard
let currentPasteboardContents = UIPasteboard.generalPasteboard().string
// you can now modify whatever was copied
let newPasteboardContent = " ----- MODIFY THE PASTEBOARD CONTENTS (\(currentPasteboardContents)) AND SET THEM HERE ---------"
// before we can actually set the new pasteboard contents, we need to make
// sure that this method isn't called recursively (we will change the pasteboard's
// contents, so if we don't remove ourselves from the observer, this method will
// be called over and over again, ending up leaving us in an endless loop)
NSNotificationCenter
.defaultCenter()
.removeObserver(
self,
name: UIPasteboardChangedNotification,
object: nil)
// GREAT! We unregistered ourselves as an observer, now's the time
// to change the pasteboard contents to whatever we want!
UIPasteboard.generalPasteboard().string = newPasteboardContent
// we want to get future changes to the pasteboard, so let's re-add
// ourselves as an observer
NSNotificationCenter
.defaultCenter()
.addObserver(
self,
selector: "pasteboardChanged:",
name: UIPasteboardChangedNotification,
object: nil)
}
}
Make sure you import MobileCoreServices otherwise you won't be able to use some of the code...
Good luck!
EDIT
If you'd like to go a less "hacky" route, I'd suggest you tap into UIMenuController. There is a nice tutorial/guide here:
http://nshipster.com/uimenucontroller/

Related

How to determine whether current view controller is active, and execute code if active

In my app, there is a ViewController.swift file and a popupViewController.swift file. Inside the app, when I open the popupViewController with storyboard segue as presentModally and then come back from popupViewController to ViewController with the code dismiss(), the methods viewDidLoad, viewWillAppear, viewDidAppear, ViewWillLayoutSubviews etc. nothing works, they execute just once and don't repeat when I go and return back. So, I want to execute the code every time when viewController.swift is active. I couldn't find a useful info in stackoverflow about this.
Meanwhile, I don't know much about notification and observers(if certainly needed), therefore, can you tell step by step in detail how to do that in Swift (not objective-c)? I mean how to determine if current view controller is active.
Edit: I am navigating from StoryBoard segue, presentModally. There is no Navigation Controller in storyboard.
I tried some codes but nothing happens. The point I came so far is:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector:#selector(appWillEnterForeground), name:UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appWillEnterForeground() {
print("asdad") //nothing happens
if self.viewIfLoaded?.window != nil {
// viewController is visible
print("CURRENT VİEW CONTROLLER") //nothing happens
}
}
As mention in my comments, I don't use storyboards. There may be a way to create an unwind segue - or maybe not - but [here's a link][1] that may help you with a storyboard-only way of fixing your issue. A quick search on "modal" turned up 9 hits, and the second one starts going into details.
I'm thinking the issue is with what modality is. Basically, your first view controller, which properly executed viewDidAppear, is still visible. So it's effectively not executing viewDidDisappear when your second VC is presented.
You might want to change your concept a bit - an application window (think AppDelegate and/or SceneDelegate become active, where a UIViewController has a is initialized and deinitialized, along with a root UIView that is loaded, appears* and disappears*. This is important, because what you want to do is send your notification from the modal VC's viewDidDisappear override.
First, I find it easiest to put all your notication definitions in an extension:
extension Notification.Name {
static let modalHasDisappeared = Notification.Name("ModalHasDisappeared")
}
This helps not only reduce string typos but also is allows Xcode's code completion to kick in.
Next, in your first view controller, ad an observer to this notification:
init() {
super.init(nibName: nil, bundle: nil)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
#objc func modalHasDisappeared() {
print("modal has disappeared")
}
I've added both forms of init for clarity. Since you are using a storyboard, I'd expect that init(coder:) is the one you need.
Finally, just send the notification when the modal has disappeared:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: .modalHasDisappeared, object: nil, userInfo: nil)
}
This sends no data, just the fact that the modal has disappeared. If you want to send data - say, a string or a table cell value, change the object parameter to it:
NotificationCenter.default.post(name: .modalHasDisappeared, object: myLabel, userInfo: nil)
And make the following changes in your first VC:
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared(_:)), name: .modalHasDisappeared, object: nil)
#objc func modalHasDisappeared(_ notification:Notification) {
let label = notification.object as! UILabel!
print(label.text)
}
Last notes:
To repeat, note that by declaring an extension to Notification.Name, I've only have one place where I'm declaring a string.
There is no code in AppDelegate or SceneDelegate, nor any references to `UIApplication(). Try to think of the view (and view controller) as appearing/disappearing, not background/foreground.
While the first view is visually in the background, it's still visible. So the trick is to code against the modal view disappearing instead.

Detect if user copied text from textfield

I want to use the word that user copies from my app textfield.
I found this code:
NotificationCenter.default.addObserver(self, selector: #selector(clipboardChanged), name: UIPasteboard.changedNotification, object: nil)
But 'clipboardChanged' function not calling when copying text from textfield.
I use Swift 4.2
This Code Just Worked Fine For Me:
override func copy(_ sender: Any?) {
super.copy()
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(clipboardChanged), name: UIPasteboard.changedNotification, object: nil)
}
#objc func clipboardChanged(){
print("Cut/Copy Performed")
}
There are many ways of achieving copy Notification
1. UIMenuController
Displays a menu with Copy, Cut, Paste, Select, and Select All commands above or below the selection.
Refer:
https://developer.apple.com/documentation/uikit/uimenucontroller
https://nshipster.com/uimenucontroller/
2. UIResponderStandardEditActions Protocol
Responders implement methods declared in this informal protocol to handle the chosen menu commands (for example, copy: and paste:). Since your UIViewController inherits from UIResponder which indeed conforms to UIResponderStandardEditActions, so it will give you error saying Redundant conformance. So just implement the methods you need directly.
Refer: https://developer.apple.com/documentation/uikit/uiresponderstandardeditactions
3. UIPasteboard changedNotification
class let changedNotification: NSNotification.Name
This happens at the same time the pasteboard’s change count (changeCount property) is incremented. Changes include the addition, removal, and modification of pasteboard items.
Refer: https://developer.apple.com/documentation/uikit/uipasteboard

Way to check data modification across many viewcontroller without using global variables?

I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?
Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.
Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}

Observer never called

I have two functions
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserverForName("personalDataDidLoad", object: self, queue: NSOperationQueue.mainQueue()) {_ in
print("Received notification")
self.showPersonalData()
}
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
but for some reason this outputs
personal data loaded
instead of the expected
personal data loaded
Received notification
I'm probably missing something obvious, but I don't see it right now....
I also tried addObserver with selector: "showPersonalData:" but this throws an unrecognized selector exception..
The problem is with the 2nd parameter in postNotificationName and addObserverForName: object. When you add an observer, and pass a non-nil object value, this means that the observer block will run when a notification comes from that object, and from that object only.
However, when you fire the notification, you do object: nil. So your notification is ignored.
On the other hand, passing a nil value for object means, "I want to receive this notification regardless of who sends it".
So you need to make sure the object value is the same in both places: either self or nil.
Is there a reason you need to use addObserverForName(_:object:queue:usingBlock:)?
Try this instead:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, "personalDataDidLoadNotification:", name: "personalDataDidLoad" object: nil)
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
}
func personalDataDidLoadNotification(notification: NSNotification) {
print("notification recieved")
}
Another answer to the title question (but not this example) but will hopefully help others in the situation I have been in for the last 3 hours:
Make sure your notificationcenter observer is added inside a class that has a persisted instance. I created the observer inside a class that was called as e.g. MyClass().setupNotification inside a local method of another class.
This meant the observer was immediately deleted and didnt persist against any instance.
Schoolboy error - but hope this helps others searching for this.

Passing data between viewControllers in swift

I was wondering if their were ways to send data to another view as soon as one view loads. I'm trying to make an app that stores something like a highScore but the only way to pass the data from the gameOver view to the mainMenu is if they click main menu and using the prepareForSegue method. Is their a way I could send it in like the viewDidLoad function?
Thanks!
According you use an UINavigationController, GameOverViewController
On your main menu add this code :
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let navigationController = segue.destinationViewController as UINavigationController
let gameOverVC = navigationController.topViewController as GameOverViewController
gameOverVC.highScore = 26
}
If you don't use an UINavigationController :
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let gameOverVC = segue.destinationViewController as GameOverViewController
gameOverVC.highScore = 26
}
Then, add your highScore var : var highScore: NSString! like this :
class GameOverViewController: UIViewController {
var highScore: NSString!
override func viewDidLoad() {
println(self.highScore)
}
}
It should works.
If not, please add more code in your post.
EDIT :
By this way, you will just pass data forward, not backward. To pass backward use Delegation, Singleton, NSNotificationCenter or NSUserDefaults
To pass data backward side, i.e. from second view to first, there are option like Delegation Pattern and NSNotificationCenter.
Suppose we using NSNotificationCenter.
In your case, We need to Send highScore to MainMenu from GameOver Class.
Register your notification.(in viewDidLoad of MainMenu Class)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateHighScoreFromGameOver:", name: "notificationUpdateHighScoreFromGameOver", object: nil)
Create method to handle posted notification. (in MainMenu Class)
#objc func updateHighScoreFromGameOver(notification: NSNotification){
NSNumber highScore = notification.object;
}
Post Notification When your game is Over(next line after displaying game over message).
NSNotificationCenter.defaultCenter().postNotificationName("notificationUpdateHighScoreFromGameOver", object: hightScore_ofTypeNSNumber)
Remove observer once you are done. (in viewWillAppear of MainMenu Class)
NSNotificationCenter.defaultCenter().removeObserver(self name: "notificationUpdateHighScoreFromGameOver", object: nil);
//alternatively you can remove all observers on this object:
NSNotificationCenter.defaultCenter().removeObserver(self);
Note : If you will not remove previous added observer and add it again, the method will be called two times. If you add same observer third time, the method will be called three times, and so on.