MFMailComposeViewController Subviews

  • MFMailComposeViewController Subviews

    Durch einen kleinen "Hack" mache ich das Message-Feld eines MFMailComposeViewController nach dem Anzeigen zum First Responder. Dabei hangle ich mich durch die Subviews:

    Brainfuck-Quellcode

    1. if ([device userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    2. [self presentModalViewController:picker animated:YES];
    3. [[[[[[[[[[[[[[[[picker view] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:1] subviews] objectAtIndex:0] becomeFirstResponder];
    4. } else {
    5. ...
    6. [popoverController presentPopoverFromRect:CGRectMake(244.0, 274.0, 280.0, 62.0) inView:[self view] permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
    7. [[[[[[[[[[[[[[[[[[picker view] subviews] objectAtIndex:1] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:0] subviews] objectAtIndex:1] subviews] objectAtIndex:0] becomeFirstResponder];
    8. }


    Wie kann ich nun verhindern, dass die App abstürzt, wenn sich in einem neuen OS etwas an der View-Hierarchie ändert? Oder gibt es eine Möglichkeit, irgendwie anders an das Message-Feld zu kommen?
  • Du kannst das mit einer Tiefen- oder Breitensuche lösen. Diese Methode such alle Textfelder und -views in einer Hierarchie:

    Quellcode

    1. - (NSArray *)collectResponders {
    2. NSMutableArray *theQueue = [NSMutableArray arrayWithCapacity:40];
    3. NSMutableArray *theResponders = [NSMutableArray arrayWithCapacity:40];
    4. [theQueue addObject:self];
    5. while([theQueue count] > 0) {
    6. UIView *theView = [theQueue objectAtIndex:0];
    7. [theQueue removeObjectAtIndex:0];
    8. if([theView isKindOfClass:[UITextView class]] || [theView isKindOfClass:[UITextField class]]) {
    9. [theResponders addObject:theView];
    10. }
    11. for(UIView *theItem in theView.subviews) {
    12. [theQueue addObject:theItem];
    13. }
    14. }
    15. return [[theResponders copy] autorelease];
    16. }
    Alles anzeigen


    Wenn Du nur den ersten brauchst, kannst Du auch das Array theResponders rausschmeissen und statt [theResponders addObject:theView]; ein return theView; einfügen. Das letzte Return sollte dann nil zurückgeben.
    „Meine Komplikation hatte eine Komplikation.“
  • macmoonshine schrieb:

    Du kannst das mit einer Tiefen- oder Breitensuche lösen. Diese Methode such alle Textfelder und -views in einer Hierarchie:

    Quellcode

    1. - (NSArray *)collectResponders {
    2. NSMutableArray *theQueue = [NSMutableArray arrayWithCapacity:40];
    3. NSMutableArray *theResponders = [NSMutableArray arrayWithCapacity:40];
    4. [theQueue addObject:self];
    5. while([theQueue count] > 0) {
    6. UIView *theView = [theQueue objectAtIndex:0];
    7. [theQueue removeObjectAtIndex:0];
    8. if([theView isKindOfClass:[UITextView class]] || [theView isKindOfClass:[UITextField class]]) {
    9. [theResponders addObject:theView];
    10. }
    11. for(UIView *theItem in theView.subviews) {
    12. [theQueue addObject:theItem];
    13. }
    14. }
    15. return [[theResponders copy] autorelease];
    16. }
    Alles anzeigen


    Wenn Du nur den ersten brauchst, kannst Du auch das Array theResponders rausschmeissen und statt [theResponders addObject:theView]; ein return theView; einfügen. Das letzte Return sollte dann nil zurückgeben.


    Nur eine kleine Frage: Warum füllst Du das mit einer Schleife?

    Einfacher wäre das doch so:

    Quellcode

    1. [theQueue addObjectsFromArray: theView.subviews];
    I would be embarrassed if they did not spy on me.
  • macmoonshine schrieb:

    longW schrieb:

    Nur eine kleine Frage: Warum füllst Du das mit einer Schleife?

    Das ist eine nicht-rekursive Version eines rekursiven Verfahrens. Das Array übernimmt dabei die Aufgabe des Stacks. Für jeden Subview werden dabei auch wieder die Subviews besucht und so weiter.

    de.wikipedia.org/wiki/Breitensuche


    Das war viel simpler gemeint.
    Anders: Was ist der Unterschied zwischen Deinem Code:

    macmoonshine schrieb:

    Quellcode

    1. for(UIView *theItem in theView.subviews) {
    2. [theQueue addObject:theItem];
    3. }


    und meinem Vorschlag:

    Quellcode

    1. [theQueue addObjectsFromArray: theView.subviews];
    I would be embarrassed if they did not spy on me.
  • Weil ich macmoonshine's Methode zu kompliziert finde, habe ich mir etwas eigenes gebastelt
    (hat auch nicht funktioniert weil MFComposeBodyField nichts mit UITextView zu tun hat):

    Quellcode

    1. - (void)setFirstResponder:(UIView *)view {
    2. for (UIView *subview in [view subviews]) {
    3. if ([subview class] == NSClassFromString(@"MFComposeBodyField")) {
    4. [subview becomeFirstResponder];
    5. return;
    6. }
    7. if ([[subview subviews] count] > 0) {
    8. [self setFirstResponder:subview];
    9. }
    10. }
    11. }
    Alles anzeigen


    Aufgerufen wird die Methode nach dem Anzeigen des MFMailComposeViewController:
    [self setFirstResponder:[picker view]];
    wobei picker der MFMailComposeViewController ist.

    Eine Frage: Wie kann ich die Schleife stoppen, wenn der Subview gefunden wurde? return scheint hier nichts zu bringen.
  • Du kannst meinen Code auch auf MFComposeBodyField umändern. Du hast bloß eine rekursive Version. Du kannst über einen boolschen Rückgabewert Dein Verfahren vorzeitig abbrechen:

    Quellcode

    1. - (BOOL)applyFirstResponder:(UIView *)view {
    2. for (UIView *subview in [view subviews]) {
    3. if([subview class] == NSClassFromString(@"MFComposeBodyField")) {
    4. [subview becomeFirstResponder];
    5. return YES;
    6. }
    7. else if([self applyFirstResponder:subview]) {
    8. return YES;
    9. }
    10. return NO;
    11. }
    Alles anzeigen

    Mit MFComposeBodyField erzeugst Du aber wieder eine Abhängigkeit zur iOS-Version. Versuch' mal

    Quellcode

    1. if([subview conformsToProtocol:@protocol(UITextInputTraits)]) {
    2. ...
    3. }
    „Meine Komplikation hatte eine Komplikation.“
  • Out-of-the-box funktioniert dein Code leider nicht. Ein paar Modifikationen waren notwendig:

    Quellcode

    1. - (BOOL)applyFirstResponder:(UIView *)view {
    2. for (UIView *subview in [view subviews]) {
    3. if ([subview class] == NSClassFromString(@"MFComposeBodyField")) {
    4. NSLog(@"MFComposeBodyField found.");
    5. [subview becomeFirstResponder];
    6. return YES;
    7. } else if ([[subview subviews] count] > 0) {
    8. NSLog(@"Subview has subviews.");
    9. if ([self applyFirstResponder:subview]) {
    10. NSLog(@"applyFirstResponder == YES");
    11. return YES;
    12. }
    13. }
    14. }
    15. NSLog(@"Not found.");
    16. return NO;
    17. }
    Alles anzeigen


    Allerdings sieht es im NSLog so aus:

    Quellcode

    1. 2011-05-27 11:31:31.149 Remote[1200:207] Subview has subviews.
    2. 2011-05-27 11:31:31.150 Remote[1200:207] Subview has subviews.
    3. 2011-05-27 11:31:31.150 Remote[1200:207] Subview has subviews.
    4. 2011-05-27 11:31:31.151 Remote[1200:207] Subview has subviews.
    5. 2011-05-27 11:31:31.151 Remote[1200:207] Subview has subviews.
    6. 2011-05-27 11:31:31.152 Remote[1200:207] Subview has subviews.
    7. 2011-05-27 11:31:31.152 Remote[1200:207] Subview has subviews.
    8. 2011-05-27 11:31:31.152 Remote[1200:207] Subview has subviews.
    9. 2011-05-27 11:31:31.153 Remote[1200:207] Not found.
    10. 2011-05-27 11:31:31.153 Remote[1200:207] Not found.
    11. 2011-05-27 11:31:31.154 Remote[1200:207] Subview has subviews.
    12. 2011-05-27 11:31:31.154 Remote[1200:207] Not found.
    13. 2011-05-27 11:31:31.155 Remote[1200:207] Subview has subviews.
    14. 2011-05-27 11:31:31.155 Remote[1200:207] Subview has subviews.
    15. 2011-05-27 11:31:31.155 Remote[1200:207] Not found.
    16. 2011-05-27 11:31:31.156 Remote[1200:207] Not found.
    17. 2011-05-27 11:31:31.156 Remote[1200:207] Not found.
    18. 2011-05-27 11:31:31.157 Remote[1200:207] Subview has subviews.
    19. 2011-05-27 11:31:31.157 Remote[1200:207] MFComposeBodyField found.
    20. 2011-05-27 11:31:31.166 Remote[1200:207] applyFirstResponder == YES
    21. 2011-05-27 11:31:31.167 Remote[1200:207] applyFirstResponder == YES
    22. 2011-05-27 11:31:31.167 Remote[1200:207] applyFirstResponder == YES
    23. 2011-05-27 11:31:31.168 Remote[1200:207] applyFirstResponder == YES
    24. 2011-05-27 11:31:31.168 Remote[1200:207] applyFirstResponder == YES
    25. 2011-05-27 11:31:31.168 Remote[1200:207] applyFirstResponder == YES
    26. 2011-05-27 11:31:31.169 Remote[1200:207] First responder set.
    Alles anzeigen


    Warum ist applyFirstResponder so oft YES? Dürfte es doch eigentlich nur ein Mal sein!?

    Quellcode

    1. if ([subview conformsToProtocol:@protocol(UITextInputTraits)]) {
    2. NSLog(@"%@", subview);
    3. }


    Liefert mir gleich vier Views.

    Mir ist es lieber, der First Responder wird halt nicht gesetzt, als dass meine App abstürzt. Kann man ja dann in einem Update beheben.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von AR.DDev ()