The problem
On iOS 10.2
didSelect() is only called when select a message for the first time, but not for the second time selecting the same message (right after the first select happened). For example:
Click a received MSMessage Message_A while my message app is active, didSelect() method is correctly called and app transit to extended view.
Click down arrow to bring app back to compressed view.
Click the same message - Message_A again, this time didSelect() isn't triggered.
Words From Apple
func didSelect(MSMessage, conversation: MSConversation)
Invoked after the system updates the conversation’s selectedMessage property in response to the user selecting a message object in the transcript.
My thought
It seems selectedMessage isn't updated when we click that message the second time (because it was already set in the first click), thus didSelect() isn't called.
Question
Am I doing it wrong?
Is there a way to work around and call didSelect() as long as a selection happens? selectedMessage is read only...
Or is there a way to make message expire (disappear) from conversation immediately after user opens (clicks) the message?
I'm afraid it is a bug, there's an open radar for that (or it is done 'by design', taking into account how much time passed since the issue had been filed).
Nevertheless, when message is selected, iMessage's extension is trying to move to expanded mode and calls willTransition(to presentationStyle:) delegate method (which appears to be another bug or cool-thing-by-design). By checking whether the expanded controller has been already shown and tuning your custom flags you may do the trick, although it is not reliable in some cases.
I have the same problem, the didSelect() and willSelect() methods are called only once.
I circumvented this problem by implementing my logic in the method :
Objective C
-(void)didBecomeActiveWithConversation:(MSConversation *)conversation
Swift
func didBecomeActive(with conversation: MSConversation)
#degapps,
Here is a workaround: After first click on message, didSelect() will take you to expanded view. Now, if a transition to compact view happens, we dismiss this app. It's not a good solution and unlikely to work for most of applications.
override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
if (presentationStyle == .compact) {
if let _ = self.activeConversation?.selectedMessage {
self.dismiss()
}
}
}
Related
Within a asynchronously executed block/closure, I want to get a check on my current state before I executed anything within that block.
A common example of where this presents itself is segueing to the next View Controller after a NSURLsession request.
Here's an example:
#IBAction func tappedButton(sender: UIButton) {
//This closure named fetchHistorical goes to the internet and fetches an array
//The response is then sent to the next view controller along with a segue
Order.fetchHistorical(orderID, completionHandler: { (resultEnum) -> () in
switch resultEnum {
case .Success(let result):
let orderItemsArray = result.orderItems!.allObjects
self.performSegueWithIdentifier("showExchanges", sender: orderItemsArray)
default:
let _ = errorModal(title: "Error", message: "Failed!")
}
})
}
Assume that the user has been impatient and tapped this button 3 times.
That would mean this function will be called three times and each time it would attempt to segue to the next view controller (iOS nicely blocks this issue with "Warning: Attempt to present on whose view is not in the window hierarchy!")
I wanted to know how do you folks tackle this problem? Is it something like ... within the closure, check if you are still in the present viewcontroller ... if you are, then segueing is valid. If not, you probably have already segued and don't execute the segue again.
***More generally, how are you checking the current state within the closure because the closure is executed asynchronously?
Since the closure isn't executing on the main thread the state would be in accurate if you check it here (as you stated). You can use GCD to go to the main thread and check the state there. There are a couple of ways you can keep this code from running multiple times. If it will take some time to perform the calculations you can use an acitivity indicator to let the user know the app is busy at the moment. If you want the user to still have the option of pressing the button you can put a tag like:
var buttonWasTapped:Bool = false //class property
#IBAction func tappedButton(sender: UIButton) {
if !self.buttonWasTapped{
self.buttonWasTapped = true
}
}
Then change it back to false on viewDidAppear so they can press once every time that page is shown.
When starting some task that will take some time to complete I would do two things;
Show some sort of activity indicator so that the user knows something is happening
Disable the button so that there is further indication that the request has been received and to prevent the user from tapping multiple times.
It is important that you consider not only the correct operation of your app but also providing a good user experience.
I have a simple app in Swift with just a few views:
A UIWebView
some TableViews
and another view with some data I download from my server
It all works well until when using the app I press the home button, leave there for a while then the iPad goes on sleep mode. A few days later I tap on the app icon and it won't start:
first tap on the icon will select the icon (goes a little darker) and deselect it a few seconds later
second tap will launch the LaunchScreen and crash a few seconds later
double tap the home button and quit the app will sometimes work
I'm just wondering if there is something I need to set on my code to handle idle/long periods of inactivity in something like viewWillDisappear or other methods?
If so I already have this in all my controllers:
override func viewWillDisappear(animated: Bool) {
timer.invalidate()
webView.removeFromSuperview()
}
Maybe I need to call super. in there too? or something else I'm missing?
You should definitely call super in your viewWillDisappear(animated:) method. See UIViewController Class Reference documentation. Also you might want to confirm why you are removing your webView from the view controller's hierarchy.
Discussion
This method is called in response to a view being removed
from a view hierarchy. This method is called before the view is
actually removed and before any animations are configured.
Subclasses can override this method and use it to commit editing
changes, resign the first responder status of the view, or perform
other relevant tasks. For example, you might use this method to revert
changes to the orientation or style of the status bar that were made
in the viewDidDisappear: method when the view was first presented. If
you override this method, you must call super at some point in your
implementation.
You probably have some null pointer exception and crash. Maybe you are calling some variable that is not set (and checked if not null).
Try disabling app funcionality (like downloading, storing and using data from server) and see where you app starts working normal again and then procede from there.
Sorry for vague answer but withouth code and maybe some log it is really hard to give specific answer.
And NO, you dont have to do anything special to handle idle/long periods of inactivity.
I am new to iOS /swift programming and I am working on an app developed by someone else, fixing some bugs.
The app is essentially a music player and the music has to be played also in background, giving the possibility to play/pause/skip from the lock screen. The app has several views, one of them, the main one, contains all the code related to the player itself (player.swift), the other ones containing other additional pages/features.
The commands from the lock screen works only when I lock the screen starting from the main view, if I do it starting from another view (e.g. the help view, which is just a page which is displayed over the player when the help link is tapped) they don't work. Reading several articles here I've realized that the reason is that the related code is in player.swift:
override func remoteControlReceivedWithEvent(event: UIEvent) {
if (event.type == UIEventType.RemoteControl) {
switch (event.subtype) {
case UIEventSubtype.RemoteControlPlay:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlPause:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlTogglePlayPause:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlNextTrack:
onNext(nil)
default:
break
}
}
}
so I have understood the problem, but even if I've read several related articles (including remoteControlReceivedWithEvent called on iOS 7.0 device but not iOS 8.0, Using lock screen for my app?, Swift. Receive remote control events to work with MPNowPLayingInfoCenter) I can't figure out where do I need to move this code and if I need to move something else or make modifications.
EDIT. I moved the code in AppDelegate.swift (deleting the code in player.swift) , as suggested. It seems it now intercepts commands even if I lock the device from a view different than player.swift. I have two problems, though:
1) It seems it works just once, If I click on "next" from the lock screen I can see from a debug string that the command is intercepted, If I do it a second time nothing happens
2) I need to call the methods (onPlayPause and onNext) in player.swift from AppDelegate.swift, I guess those methods expect to have a player object set and/or they refer to variables declared in player.swift and I don't know how to handle this. For example the onNext method is declared as
#IBAction func onNext(sender: AnyObject?) {
oldImage = iAlbumArt.image
.......
and if I call the method as a new instance from AppDelegate
player().onNext(nil)
I get an error because iAlbumArt.image is NIL. iAlbumArt is a variable declared in the Player class as
#IBOutlet weak var iAlbumArt: UIImageView!
Sorry for the naive questions but I've been looking into iOS development just since a couple of weeks ago.
Try adding it to your App Delegate class.
Edit:
To forward remote control events to the view controller, add this code to the app delegate (assuming your player view controller is called PlayerViewController):
let vcs = (self.window!.rootViewController as! UINavigationController).viewControllers
let indexOfPlayer = (vcs as! NSArray).indexOfObjectPassingTest { (vc, idx, stop) in
return (vc.isKindOfClass(PlayerViewController))
}
let playerVC = vcs[indexOfPlayer];
Edit 2:
Place the override func remoteControlReceivedWithEvent method in your App Delegate Class.
At the top of that method, place the code snippet shown above.
In that method, replace self with playerVC.
In your player view controller, add code to respond to onPlayPause and onNext functions.
Note:
The reason why this code:
player().onNext(nil)
was throwing an error was because player() creates a brand-new instance of your player class. You want to use the existing instance so the changes get reflected on the screen.
I am new to Xcode but have been developing for a 15 years. I am trying to understand how event declaration works in Xcode. Could it possibly be that you can only declare when you Ctr drag it to the code? And then only view it via the dialog box in the Connection Viewer? That would be terribly annoying. What am I missing? Surely the event should appear in the code somewhere.
Here is a sample function that is supposedly declaring a "Did End on Exit" code.
#IBAction func helloAction(sender: UITextField) {
nameLabel.text = "Hi \(sender.text)"
}
Thanks for your help.
The helloAction method is not declaring a "Did End on Exit" code, unless I'm misunderstanding what you mean by declaring. helloAction is a target that's called when the control it's bound to in Interface Builder receives a Did End on Exit event.
This uses a mechanism known as the Target-Action pattern in Cocoa. A control receives an event, and then calls any methods that have registered to be notified when the control receives that event.
This relationship can be created programmatically as well. Consider the following.
let button = UIButton()
button.addTarget(self, action: "tapButton:", forControlEvents:UIControlEvents.TouchUpInside)
When the button is tapped and released with the touch still inside the buttons bounds, self's tapButton: method is called.
If you want to learn more about Target-Action in Cocoa, check out a high level overview or the definitive discussion, both from Apple.
I'm working on iOS 8 custom keyboard extension right now, and there are some issues that I cannot figure out.
First, I think the UITextInputDelegate Methods are not working as I expected.
Does this sound right: selectionWillChange: and selectionDidChange: methods should be called when user long-presses typing area? And textWillChange: and textDidChange: methods should be called whenever the text is literally changing?
Actually, what I observed is that, when I changed selection in text input area, textWillChange: and textDidChange: are called, and I cannot get a clue that the other two methods are called in what condition. If anyone knows about the usage of these delegate methods, please let me know.
Second, I know the playInputClick: method can be used to virtualize keyboard click sound in custom keyboard. As this is applicable in a normal situation, I found it impossible to apply in iOS 8 custom keyboard extension. My app consists of one keyboard view controller, and custom view that subclasses UIView is added to this view controller. My approach is that UIInputViewAudioFeedback delegate is declared in this custom view, enableInputClicksWhenVisible method is returning YES, class method that calls [[UIDevice currentDevice] playInputClick] is set, then this method is called wherever the keyboard sound is needed: which is not working at all.
Is my approach is wrong in any way? If anyone has succeeded in using playInputClick method, please share your wisdom.
Thank you
It is best to play the Audio on a queue rather than in the UI key handler
func playPressKeySound() {
let PRESS_KEY_DEFAULT_SOUND_ID: SystemSoundID = 1104
dispatch_async(dispatch_get_main_queue()) {
AudioServicesPlaySystemSound(PRESS_KEY_DEFAULT_SOUND_ID)
}
}
NOTE: this only works if the keyboard has Full Access switched on, so best test there is full access before calling, otherwise the keyboard can suffer long pauses.
For the second question, try AudioServicesPlaySystemSound
#define PRESS_KEY_DEFAULT_SOUND_ID 1104
- (void)playPressKeySound {
if (self.openPressSound) {
AudioServicesPlaySystemSound(PRESS_KEY_DEFAULT_SOUND_ID);
}
}