[iOS] Storyboard verbeteren en async afbeeldingen ophalen

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • JJ93
  • Registratie: Maart 2013
  • Laatst online: 15-09 15:57

JJ93

Error 418

Topicstarter
Ik ben bezig met een iOS applicatie geschreven in Objective C, gemaakt voor iPad en iPhone draaiend op iOS 7+.

Storyboard
In mijn Storyboard maak ik nu drie keer gebruik van dezelfde scene om gegevens te tonen.
Afbeeldingslocatie: http://i.imgur.com/iOEEaAc.png
Ik heb geprobeerd om hier één scene van te maken en alle drie de UINavigationControllers te koppelen aan deze enkele scene. Dit geeft de volgende fout:

"Cannot display a nested UINavigationController with zero viewControllers"
Ik snap niet helemaal waarom dit fout gaat, want de UINavigationControllers zijn niet aangepast en verwijzen alle drie alsnog naar dezelfde root view controller.

Ik wil dus zo min mogelijk gekopieerd en geplakte scenes hebben binnen mijn Storyboard. Hoe kan ik dit het beste bereiken? De oplossing moet ook werken met een UISplitViewController op de iPad.

Async afbeeldingen ophalen
Voor het ophalen van afbeeldingen heb ik het volgende stukje code geschreven:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Foto ophalen
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            // Data omzetten naar UIImage
            NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
            UIImage *image = [UIImage imageWithData:imageData];
            
            if(image != nil) {
                // Foto opslaan
                [self saveImage:image];
                
                // Foto weergeven
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self addImage:image];
                });
            }
        });

Bij Android worden AsyncTasks altijd één voor één uitgevoerd, maar ik merk dat deze op iOS allemaal tegelijk worden uitgevoerd. Heeft Apple er zelf voor gezorgd dat threading verder goed verloopt en eventueel in groepjes indien het gebruikte toestel niet met 10 threads tegelijk plaatjes kan ophalen? Of moet ik hier zelf nog even naar kijken.

Verder vroeg ik mij af of ik zo dispatch_async op de juiste manier gebruik. In de background haal ik het plaatje op, sla ik hem op, en maak ik in core data een verwijzing. Vervolgens vraag ik toegang tot de main thread om het plaatje toe te voegen aan een UIScrollView.

Klopt dit zo of kan ik hier nog wat aan verbeteren?
Groeten,
JJ

Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Disclaimer : I am not an Apple programmer :)
JJ93 schreef op dinsdag 30 juni 2015 @ 22:15:
Bij Android worden AsyncTasks altijd één voor één uitgevoerd, maar ik merk dat deze op iOS allemaal tegelijk worden uitgevoerd. Heeft Apple er zelf voor gezorgd dat threading verder goed verloopt en eventueel in groepjes indien het gebruikte toestel niet met 10 threads tegelijk plaatjes kan ophalen? Of moet ik hier zelf nog even naar kijken.
Ik zou denken dat je hier nog zelf even naar moet kijken. In wezen weet Apple op voorhand niet of jij 1000 plaatjes wilt ophalen of 1000 status-calls, terwijl het qua geheugengebruik etc heel veel uitmaakt.
Dus of er is een functie met een heel erg duidelijke specifieke naam voor het ophalen van plaatjes, of er is niets automagisch geregeld en het is aan de programmeur om het juist in te zetten.
Verder vroeg ik mij af of ik zo dispatch_async op de juiste manier gebruik. In de background haal ik het plaatje op, sla ik hem op, en maak ik in core data een verwijzing. Vervolgens vraag ik toegang tot de main thread om het plaatje toe te voegen aan een UIScrollView.
Nogmaals : Ik ben geen Apple programmeur, maar je uitleg is wmb al te omslachtig, dus dat moet beter kunnen. Ik zou zeggen google eens op rubber duck en schaf die aan, die zal wonderen verrichten. Want als je het niet kan uitleggen zal het niet goed zijn.

Qua code voorbeeld zit ik eigenlijk voornamelijk met de vraag waarom je een dispatch_async binnen een dispatch_async doet. Is die 2e disbatch_async call echt zo zwaar dat je hem async moet draaien (je slaat net daarvoor het plaatje nog niet async op).
Async handelingen kennen ook gewoon overhead etc, die zijn over het algemeen ook niet cheap.

Wellicht dat het de methode bij Apple is, maar wmb is het wel een mogelijke code-smell.

Acties:
  • 0 Henk 'm!

  • Scott
  • Registratie: December 2004
  • Laatst online: 01:25

Scott

Ik ben, dus ik tweak

JJ93 schreef op dinsdag 30 juni 2015 @ 22:15:
Ik ben bezig met een iOS applicatie geschreven in Objective C, gemaakt voor iPad en iPhone draaiend op iOS 7+.

Storyboard
In mijn Storyboard maak ik nu drie keer gebruik van dezelfde scene om gegevens te tonen.
[afbeelding]
Ik heb geprobeerd om hier één scene van te maken en alle drie de UINavigationControllers te koppelen aan deze enkele scene. Dit geeft de volgende fout:

"Cannot display a nested UINavigationController with zero viewControllers"
Ik snap niet helemaal waarom dit fout gaat, want de UINavigationControllers zijn niet aangepast en verwijzen alle drie alsnog naar dezelfde root view controller.

Ik wil dus zo min mogelijk gekopieerd en geplakte scenes hebben binnen mijn Storyboard. Hoe kan ik dit het beste bereiken? De oplossing moet ook werken met een UISplitViewController op de iPad.
De foutmelding die je krijgt doet me vermoeden dat je iets fout doet. Wat je wil is zeker mogelijk, maar je screenshot is te klein om een goed idee te krijgen van wat je daar doet. Zet of je storyboard even online of maak een wat groter screenshot.
Async afbeeldingen ophalen
Voor het ophalen van afbeeldingen heb ik het volgende stukje code geschreven:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Foto ophalen
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            // Data omzetten naar UIImage
            NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
            UIImage *image = [UIImage imageWithData:imageData];
            
            if(image != nil) {
                // Foto opslaan
                [self saveImage:image];
                
                // Foto weergeven
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self addImage:image];
                });
            }
        });

Bij Android worden AsyncTasks altijd één voor één uitgevoerd, maar ik merk dat deze op iOS allemaal tegelijk worden uitgevoerd. Heeft Apple er zelf voor gezorgd dat threading verder goed verloopt en eventueel in groepjes indien het gebruikte toestel niet met 10 threads tegelijk plaatjes kan ophalen? Of moet ik hier zelf nog even naar kijken.

Verder vroeg ik mij af of ik zo dispatch_async op de juiste manier gebruik. In de background haal ik het plaatje op, sla ik hem op, en maak ik in core data een verwijzing. Vervolgens vraag ik toegang tot de main thread om het plaatje toe te voegen aan een UIScrollView.

Klopt dit zo of kan ik hier nog wat aan verbeteren?
Groeten,
JJ
Dit lijkt me prima. Dispatch queues moet je los zien van threads, ondanks dat ze die wel gebruiken. Het is een implementatiedetail en het OS zorgt ervoor dat dat allemaal goed gaat. Het enige waar je naar zou kunnen kijken is een soort van limiet instellen zodat je niet onnodig dingen gaat downloaden en de databundel van je gebruiker verspilt met plaatjes die je toch nooit laat zien.
Gomez12 schreef op woensdag 01 juli 2015 @ 00:27:
Qua code voorbeeld zit ik eigenlijk voornamelijk met de vraag waarom je een dispatch_async binnen een dispatch_async doet. Is die 2e disbatch_async call echt zo zwaar dat je hem async moet draaien (je slaat net daarvoor het plaatje nog niet async op).
Async handelingen kennen ook gewoon overhead etc, die zijn over het algemeen ook niet cheap.

Wellicht dat het de methode bij Apple is, maar wmb is het wel een mogelijke code-smell.
Dit is inderdaad heel standaard in de Apple-wereld. De eerste dispatch_async call doet dingen op de achtergrond, maar alle UI moet in de main thread dus je maakt zodra het plaatje gedownload is meteen weer een dispatch_async call die het plaatje (op de main thread) in de UI laat zien.

Absoluut geen code smell dus :)

Acties:
  • 0 Henk 'm!

  • JJ93
  • Registratie: Maart 2013
  • Laatst online: 15-09 15:57

JJ93

Error 418

Topicstarter
Async:
Bedankt voor de reacties beide. Voor mij nu ook weer wat duidelijk hoe de Apple implementatie precies werkt. De app communiceert met mijn eigen API en de limiet van het aantal afbeeldingen wordt op mijn server al gezet.

Mogelijk kan ik wel iets doen dat plaatjes pas worden ingeladen als je ze echt kunt zien. Want in de UIScrollView zie je er namelijk maar 1 of 2, en de als iemand de plaatjes verder niet bekijkt hoeven de overige 8 ook niet opgehaald te worden.


Storyboard:
Hij is hier nu te downloaden: https://www.dropbox.com/s...gdfs/Main.storyboard?dl=0
Ik wil dus graag de Kenteken Scene (de UITableViewController met twee herbruikbare cellen) herbruiken.

Je kunt op vier manieren in deze Scene terecht komen:
1. Zoeken op kenteken in de zoekbalk
2. In de geschiedenis lijst klikken op een kenteken
3. In de favorieten lijst klikken op een kenteken
4. Zoeken op merk/model en vervolgens op een kenteken klikken

Nummer 1 en 4 gebruiken dezelfde Scene, dit gaat goed.
Nummer 2 en 3 gebruiken een copy/pasted Scene.

Wat ik heb geprobeerd is om nummer 2 en 3 ook te verwijzen naar dezelfde Scene als 1 en 4. Maar dit gaf dus de "Cannot display a nested UINavigationController with zero viewControllers" error. Ik denk zelf dat dit komt omdat ze in verschillende tabs zitten o.i.d.?

EDIT:
Ik heb de Scene van de geschiedenis en favorieten tab nu verwijdert en de UINavigationController met een root view relationship gekoppeld aan de enkele Kenteken Scene die nu nog over is.

Geeft de volgende error:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2015-07-01 11:54:24.794 KentekenInfo[866:23909] Geklikt op een cell
2015-07-01 11:54:24.796 KentekenInfo[866:23909] Prepare for segue to KentekenController
2015-07-01 11:54:24.807 KentekenInfo[866:23909] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot display a nested UINavigationController with zero viewControllers: <UINavigationController: 0x7fd4be058410>'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010ede5c65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010e6c2bb7 objc_exception_throw + 45
    2   CoreFoundation                      0x000000010ede5b9d +[NSException raise:format:] + 205
    3   UIKit                               0x000000010f341308 -[UINavigationController _prepareForNestedDisplayWithNavigationController:] + 93
    4   UIKit                               0x000000010f341baf -[UINavigationController pushViewController:transition:forceImmediate:] + 332
    5   UIKit                               0x000000010f34175c -[UINavigationController pushViewController:animated:] + 598
    6   UIKit                               0x000000010f628a7e -[UISplitViewController showDetailViewController:sender:] + 420
    7   KentekenInfo                        0x000000010e1768da -[HistoryController tableView:didSelectRowAtIndexPath:] + 154
    8   UIKit                               0x000000010f2cfdc9 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1293
    9   UIKit                               0x000000010f2cff0a -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 219
    10  UIKit                               0x000000010f20262c _applyBlockToCFArrayCopiedToStack + 314
    11  UIKit                               0x000000010f2024a6 _afterCACommitHandler + 533
    12  CoreFoundation                      0x000000010ed18ca7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    13  CoreFoundation                      0x000000010ed18c00 __CFRunLoopDoObservers + 368
    14  CoreFoundation                      0x000000010ed0ea33 __CFRunLoopRun + 1123
    15  CoreFoundation                      0x000000010ed0e366 CFRunLoopRunSpecific + 470
    16  GraphicsServices                    0x000000011231ea3e GSEventRunModal + 161
    17  UIKit                               0x000000010f1de900 UIApplicationMain + 1282
    18  KentekenInfo                        0x000000010e174def main + 111
    19  libdyld.dylib                       0x000000011139d145 start + 1
    20  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException


prepareForSegue:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Kenteken in de target ViewController plaatsen
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    NSLog(@"Prepare for segue to KentekenController");
   
    // KentekenController
    KentekenController *kentekenController = nil;
    if([segue.destinationViewController isKindOfClass:[UINavigationController class]]) {
        kentekenController = (KentekenController*) [segue.destinationViewController topViewController];
    }else{
        kentekenController = (KentekenController*) segue.destinationViewController;
    }
    
    // New kenteken
    Kenteken *kenteken = [self.fetchedResultsController objectAtIndexPath:self.selectedRow];
    [kentekenController newKenteken:kenteken];
}


EDIT II:
Ik denk dat het fout gaat omdat er vanuit meerdere tabs naar dezelfde Scene verwezen wordt.
In de "Zoek" tab wordt verwezen naar deze KentekenScene d.m.v. "Relationship root view controller". Dit gaat goed.
In de "Geschiedenis" tab en "Favorieten" tab wordt er ook naar deze Scene verwezen d.m.v. dezelfde "Relationship root view controller". Maar dit gaat dus fout bij de Segue naar de Kenteken Scene.

Waarschijnlijk dan in de war met drie verwijzingen naar dezelfde Scene in drie verschillende tabs?

EDIT III:
Hier is hoe ik mijn Storyboard heb aangepast: https://www.dropbox.com/s.../MainEdit.storyboard?dl=0
Geeft helaas dus de hierboven vermeldde error.

[ Voor 60% gewijzigd door JJ93 op 01-07-2015 12:14 ]


Acties:
  • 0 Henk 'm!

  • JJ93
  • Registratie: Maart 2013
  • Laatst online: 15-09 15:57

JJ93

Error 418

Topicstarter
Helaas is het mij nog steeds niet gelukt om het Storyboard correct te laten functioneren met slechts één KentekenScene? Heeft iemand nog een idee hoe ik dat voor elkaar kan krijgen?

Acties:
  • 0 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
Kan je niet 1 superclass maken van de UIViewController en daarboven op 3 subclasses daarvan? (het is overigens wel mogelijk het geheel in runtime op te bouwen zonder visueel gebruik te maken van storyboards, alleen de constraints zullen dan nog wel een hoop irritaties opwekken). m.b.v. subclasses en segues moet het zeker wel lukken om de boel te optimaliseren.

Acties:
  • 0 Henk 'm!

  • JJ93
  • Registratie: Maart 2013
  • Laatst online: 15-09 15:57

JJ93

Error 418

Topicstarter
BoringDay schreef op vrijdag 03 juli 2015 @ 17:16:
Kan je niet 1 superclass maken van de UIViewController en daarboven op 3 subclasses daarvan? (het is overigens wel mogelijk het geheel in runtime op te bouwen zonder visueel gebruik te maken van storyboards, alleen de constraints zullen dan nog wel een hoop irritaties opwekken). m.b.v. subclasses en segues moet het zeker wel lukken om de boel te optimaliseren.
Ze doen alle drie exact hetzelfde en KentekenController eft al van UITableViewController. Het maken van een extensie zoals GeschiedenisKentekenController o.i.d. lost mijn probleem niet op.

Ik heb al een poging gedaan om alles in de code te bouwen, dat is niet zo'n probleem omdat het gewoon een UITableViewController is met twee cellen die herbruikt worden. Het zit hem dus vooral in deze twee cellen: misschien kan ik deze via een xib ofzo beschikbaar maken.

Verder ben ik op zich wel tevreden met de huidige oplossing. Alleen heb ik dus drie exacte kopieën van één Scene. Het zou mooi zijn als ik deze Scene 1x heb, en vanuit drie verschillende tabs hier naar toe kan verwijzen. Maar dat gaat dus niet goed.

In de code alles proberen te regelen gaat ook niet helemaal goed i.v.m. UISplitView implementatie op iPad en iPhone van Apple. In het Storyboard wordt dit automatisch afgehandeld.

[ Voor 21% gewijzigd door JJ93 op 03-07-2015 17:35 ]


Acties:
  • 0 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
Dan kan je ook 1 view maken daaraan de bijbehorende module koppelen en de segues zetten (bovendien heb je al de informatie bij prepareForSegue als dit goed is opgezet). Dat moet vrij eenvoudig zijn.
Voor UITableViewCell kan je aparte classes maken dit in combinatie van subclasses/protocol/delegate.
Kwestie van even verdiepen in de materie.
Bovendien zou ik de overstap maken naar swift2, uiteindelijk zal het daar naar toe gaan.

Acties:
  • 0 Henk 'm!

  • Ryur
  • Registratie: December 2007
  • Laatst online: 20:28
BoringDay schreef op vrijdag 03 juli 2015 @ 17:45:
Bovendien zou ik de overstap maken naar swift2, uiteindelijk zal het daar naar toe gaan.
Dat vind ik zeer kort door de bocht. Zeker geen Swift 2 momenteel gebruiken! Nog een taal die volop in ontwikkeling is (en Alpha status heeft?).

Je kan wel eens kijken naar Swift, maar ikzelf hou het voorlopig nog bij Obj-C.

Acties:
  • 0 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
Dat is geen alpha meer. Je kan beter tijdig erop inspelen dan te lang wachten.

Acties:
  • 0 Henk 'm!

  • JJ93
  • Registratie: Maart 2013
  • Laatst online: 15-09 15:57

JJ93

Error 418

Topicstarter
BoringDay schreef op vrijdag 03 juli 2015 @ 17:45:
Dan kan je ook 1 view maken daaraan de bijbehorende module koppelen en de segues zetten (bovendien heb je al de informatie bij prepareForSegue als dit goed is opgezet). Dat moet vrij eenvoudig zijn.
Voor UITableViewCell kan je aparte classes maken dit in combinatie van subclasses/protocol/delegate.
Kwestie van even verdiepen in de materie.
Bovendien zou ik de overstap maken naar swift2, uiteindelijk zal het daar naar toe gaan.
Elke cell heeft al een subclass en de UI componenten zijn gekoppeld.
De benodigde informatie heb ik inderdaad al bij de prepareForSegue.

Zoals te zien is in het tweede Storyboard dat te downloaden is gaat het niet zomaar goed om één Scene te hebben en vier segues naar deze Scene. Hoe zou de view implementatie van jou er uit zien?
EDIT: Zoiets als dit: https://medium.com/@musaw...les-in-xcode-9bee5824e722 ? Mijn custom cells zitten in de UITableView zelf, dus als ik ze 'er uit haal' en met xib's werk dan is mijn probleem inderdaad opgelost. Thx, ga er nu mee bezig.

Toen ik begon met de ontwikkeling was Swift2 er nog niet, ik heb ook klasgenoten gehad die een project maakten in Swift1, toen hebben ze XCode geupdate en moesten ze al hun code herschrijven, twee dagen voor de deadline.. Met Swift2 is dit een minder groot probleem maar voor nu hou ik het maar bij Objective C.

[ Voor 10% gewijzigd door JJ93 op 04-07-2015 11:59 ]


Acties:
  • 0 Henk 'm!

  • JJ93
  • Registratie: Maart 2013
  • Laatst online: 15-09 15:57

JJ93

Error 418

Topicstarter
Bedankt voor de reacties allemaal. Het is nu gelukt om het Storyboard op te bouwen met zoals ik wou.

Oud:
Afbeeldingslocatie: http://i.imgur.com/iOEEaAc.png

Nieuw:
Afbeeldingslocatie: http://i.imgur.com/48HeVQT.png

Verschil: alle cellen zitten niet meer in de UITableView / UICollectionView zelf maar hebben nu een eigen xib. In de viewDidLoad worden de gebruikte xib(s) geregistreerd:
code:
1
[self.tableView registerNib:[UINib nibWithNibName:reuseIdentifier bundle:nil] forCellReuseIdentifier:reuseIdentifier];

of
code:
1
 [self.collectionView registerNib:[UINib nibWithNibName:reuseIdentifier bundle:nil] forCellWithReuseIdentifier:reuseIdentifier];


Ik had voor elke cell al een subclass gemaakt, dus het was een kwestie van de .xibs aanmaken, en de custom cellen vanuit het storyboard kopieren naar de .xibs. Vervolgens de custom cellen in elke controller registeren, en daarna konden ze uit het Storyboard gehaald worden.

Afbeeldingslocatie: http://i.imgur.com/7NrGd6X.png

Dus het eindresultaat is dat ik alleen maar een custom class hoef in te stellen voor een UITableViewController of UICollectionViewController. Vervolgens wordt in de controller zelf de .xib die voor de cell layout moet zorgen geregistreerd. Dus geen dubbele cell layouts in het Storyboard gedefinieerd.

[ Voor 0% gewijzigd door JJ93 op 04-07-2015 13:11 . Reden: UICollectionView code ]

Pagina: 1