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.
What else can we do?
Use private apis that will most likely get us a rejection from App Store.
How to find what private apis can we use?
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)
Does it work?
As of Xcode 12.5
& iOS 14.6
- it does.
Why all the dance to get to the selector name?
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/