Hello StackOverflow friends,
I am integrating a MFMessageViewController and I want to disable the editing area of it either by disallowing the keyboard to appear or by disabling the user interaction to it. Currently my code is :
import UIKit
import MessageUI
class ViewController: UIViewController,MFMessageComposeViewControllerDelegate,UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func sendSmsClick(_ sender: AnyObject) {
guard MFMessageComposeViewController.canSendText() else {
return
}
let messageVC = MFMessageComposeViewController()
UIButton.appearance(whenContainedInInstancesOf: [MFMessageComposeViewController.self]).isUserInteractionEnabled = false
messageVC.body = "Enter a message hjhjhjkhjkhjhjhjjhgjhghjgjhghjghjghjghjgjhghjghjgjhghjghjghghjghjghjghghjghjhjghjghjghhvvvbnvhvhghghguyguygyugugigiugiouiopuopuoppuuo";
messageVC.recipients = ["Enter tel-nr"]
messageVC.messageComposeDelegate = self;
NSLog("Subviews %@", messageVC.view.subviews);
// self.view.endEditing(true)
self.present(messageVC, animated: false) {
// self.getAllSubviews(view: messageVC.view)
messageVC.view.loopViewHierarchy { (view, stop) in
if view is UIButton {
/// use the view
print("here")
stop = true
}
}
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
switch (result.rawValue) {
case MessageComposeResult.cancelled.rawValue:
print("Message was cancelled")
self.dismiss(animated: true, completion: nil)
case MessageComposeResult.failed.rawValue:
print("Message failed")
self.dismiss(animated: true, completion: nil)
case MessageComposeResult.sent.rawValue:
print("Message was sent")
self.dismiss(animated: true, completion: nil)
default:
break;
}
}
It is working fine and I just want to get to know the specific UIElement which is above the keyboard and I want to disable it for for further editing. How can I achieve this ?
You could try printing the view hierarchy and find your text field from the subviews, the problem is - all of this UI is running into a different environment than your app and you won't find anything useful there.
/// Helper for printing view hierarchy recursively
extension UIView {
func printViewHierarchy() {
print(self)
for view in self.subviews {
view.printViewHierarchy()
}
}
}
self.present(messageVC, animated: true, completion: {
// Try printing the view hierarchy after it has been loaded on to screen
messageVC.view.printViewHierarchy()
})
// Here's what's printed by above
<UILayoutContainerView: 0x100a04990; frame = (0 0; 375 627); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x28100ef10>; layer = <CALayer: 0x281eac720>>
<UINavigationTransitionView: 0x100c0e0d0; frame = (0 0; 375 627); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281eee860>>
<UIViewControllerWrapperView: 0x100c0f990; frame = (0 0; 375 627); autoresize = W+H; layer = <CALayer: 0x281e975e0>>
<UIView: 0x103b04900; frame = (0 0; 375 627); autoresize = W+H; layer = <CALayer: 0x281e8aa80>>
<_UISizeTrackingView: 0x103a05f80; frame = (0 0; 375 627); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281e84c60>>
<_UIRemoteView: 0x103a077f0; frame = (0 0; 375 667); userInteractionEnabled = NO; layer = <CALayerHost: 0x281e84b20>>
This _UIRemoteView
will get in your way and you won't be able to find your target textField
/button
in the view hierarchy.
Use private apis that will most likely get us a rejection from App Store.
ObjectiveC.runtime
provides you ways to inspect class details (public + private).
import ObjectiveC.runtime
func printClassDetails(_ targetClass: AnyClass) {
var varCount: UInt32 = 0
let iVars = class_copyIvarList(targetClass, &varCount)
var index = 0
if let iVars = iVars {
while index < varCount-1 {
let iVar = iVars[index]
if let name = ivar_getName(iVar) {
print("iVar ------------> \(String(cString: name))")
}
index += 1
}
}
free(iVars)
index = 0
let methods = class_copyMethodList(targetClass, &varCount)
if let methods = methods {
while index < varCount-1 {
let method = methods[index]
let selector = method_getName(method)
print("method ------------> \(NSStringFromSelector(selector))")
index += 1
}
}
free(methods)
}
With above code, if you try to inspect MFMessageComposeViewController
class, you will see following.
printClassDetails(MFMessageComposeViewController.self)
iVar ------------> _internal
iVar ------------> _messageComposeDelegate
iVar ------------> _recipients
iVar ------------> _body
iVar ------------> _subject
iVar ------------> _message
iVar ------------> _currentAttachedVideoCount
iVar ------------> _currentAttachedAudioCount
iVar ------------> _currentAttachedImageCount
iVar ------------> _UTITypes
iVar ------------> _photoIDs
iVar ------------> _cloudPhotoIDs
iVar ------------> _contentText
iVar ------------> _contentURLs
iVar ------------> _chatGUID
iVar ------------> _groupName
iVar ------------> _shareSheetSessionID
method ------------> recipients
method ------------> subject
method ------------> setGroupName:
method ------------> setRecipients:
method ------------> smsComposeControllerShouldSendMessageWithText:toRecipients:completion:
method ------------> attachments
method ------------> setMessageComposeDelegate:
method ------------> addRichLinkData:withWebpageURL:
method ------------> addAttachmentURL:withAlternateFilename:
method ------------> addAttachmentData:typeIdentifier:filename:
method ------------> setShareSheetSessionID:
method ------------> automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
method ------------> message
method ------------> body
method ------------> setChatGUID:
method ------------> setMessage:
method ------------> chatGUID
method ------------> viewWillDisappear:
method ------------> contentText
method ------------> setSubject:
method ------------> viewDidLoad
method ------------> attachmentURLs
method ------------> groupName
method ------------> setUTITypes:
method ------------> dealloc
method ------------> viewDidAppear:
method ------------> viewWillAppear:
method ------------> UTITypes
method ------------> setContentText:
method ------------> setBody:
method ------------> smsComposeControllerCancelled:
method ------------> smsComposeControllerSendStarted:
method ------------> smsComposeControllerEntryViewContentInserted:
method ------------> .cxx_destruct
method ------------> photoIDs
method ------------> setModalPresentationStyle:
method ------------> setPhotoIDs:
method ------------> setContentURLs:
method ------------> setCloudPhotoIDs:
method ------------> initWithNibName:bundle:
method ------------> cloudPhotoIDs
method ------------> contentURLs
method ------------> shareSheetSessionID
method ------------> disableUserAttachments
method ------------> setCurrentAttachedVideoCount:
method ------------> setCurrentAttachedAudioCount:
method ------------> setCurrentAttachedImageCount:
method ------------> _MIMETypeForURL:
method ------------> _isVideoMIMEType:
method ------------> _isAudioMIMEType:
method ------------> _isImageMIMEType:
method ------------> _contentTypeForMIMEType:
method ------------> _updateAttachmentCountForAttachmentURL:
method ------------> canAddAttachmentURL:
method ------------> mutableAttachmentURLs
method ------------> addAttachmentData:withAlternateFilename:
method ------------> insertSharedItemAndReturnEntryViewFrame:withAlternateFilename:completion:
method ------------> showSharedItemInEntryView
method ------------> _setCanEditRecipients:
method ------------> _setShouldDisableEntryField:
method ------------> messageComposeDelegate
method ------------> currentAttachedVideoCount
method ------------> currentAttachedAudioCount
_setShouldDisableEntryField
looks interesting. How to use this?let messageVC = MFMessageComposeViewController()
messageVC.body = "Enter a message"
messageVC.recipients = ["Test Telephone #"]
let name = [":","d","l","e","i","F","y","r","t","n","E","e","l","b","a","s","i","D","d","l","u","o","h","S","t","e","s","_"].reversed().joined()
let sel = NSSelectorFromString(name)
if messageVC.responds(to: sel) {
messageVC.perform(sel, with: true)
}
self.present(messageVC, animated: true, completion: nil)
As of
Xcode 12.5
&iOS 14.6
- it does.
Maybe (and that's a BIG MAYBE) it will help avoid a rejection like following.
We identified one or more issues with a recent delivery for your app. Please correct the following issues, then upload again.
ITMS-90338: Non-public API usage - The app contains or inherits from non-public classes in Project: XXXXXXXXXXXXXX . If method names in your source code match the private Apple APIs listed above, altering your method names will help prevent this app from being flagged in future submissions. In addition, note that one or more of the above APIs may be located in a static library that was included with your app. If so, they must be removed. For further information, visit the Technical Support Information at http://developer.apple.com/support/technical/