Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

Objective-C

Войти
Регистрация
Восстановить пароль
 
lowlol
2 / 2 / 2
Регистрация: 02.12.2012
Сообщений: 102
#1

Контроллер для свайпа и вынесение логики - Objective-C

23.03.2016, 14:05. Просмотров 620. Ответов 9
Метки нет (Все метки)

Пытался устроиться на позицию джуниора в одну компанию. Дали тестовое задание. Я его выполнил и отправил. Мне ответили, что в моем решении "Проблемы с тем как логика вынесена и контролером для свайпа."
Подскажите, что конкретно неправильно/некорректно в моём решении?

Задача была поставлена так:
Сделать приложение, которое по нажатию кнопки "fetch" выгружает с сервера JSON с записями через гет-запрос и заливает их в tableview. При выборе cell'a и нажатии на кнопку "details" выгружает подробную инфу о записи и показывает ее на новом экране.

Вот код:
главный вьюконтроллер
Objective-C
1
2
3
4
5
6
#import <UIKit/UIKit.h>
#import "infoDelegate.h"
 
@interface ViewController : UIViewController <infoDelegate>
 
@end
Objective-C
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#import "ViewController.h"
#import "INInfoViewController.h"
#import "INTools.h"
 
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
 
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIButton *fetchButton;
@property (weak, nonatomic) IBOutlet UIButton *detailsButton;
@property (strong, nonatomic) NSArray *identities;
@property (strong, nonatomic) NSDictionary *selectedIdentity;
 
@end
 
@implementation ViewController
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReuseID"];
    if (!cell)
    {
        cell = [tableView dequeueReusableCellWithIdentifier:@"ReuseID"];
    }
    
    return cell;
}
 
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *currentItem = self.identities[indexPath.row];
    NSString *title = [NSString stringWithFormat:@"%@ %@", [currentItem objectForKey:@"name"], [currentItem objectForKey:@"surname"]];
    cell.textLabel.text = title;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.selectedIdentity = self.identities[indexPath.row];
}
 
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 60;
}
 
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.identities count];
}
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
 
- (IBAction)fetchTriggered:(id)sender
{
    [INTools performGETRequestWithURL:@"http://someurl" completion:^void (NSData *data) {
        NSArray *responseArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
        
        self.identities = responseArray;
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
        });
    }];
    
}
 
- (IBAction)detailsTriggered:(id)sender
{
    NSString *urlString = [NSString stringWithFormat:@"http://someurl%@", [self.selectedIdentity objectForKey:@"id"]];
    [INTools performGETRequestWithURL:urlString completion:^void (NSData *data){
        NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
        self.selectedIdentity = responseDictionary;
        
        UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipedDown)];
        swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
        
        INInfoViewController *detailViewController = [[INInfoViewController alloc] initWithNibName:@"INInfoViewController" bundle:nil];
        [detailViewController.view addGestureRecognizer:swipeDown];
        
        [self presentViewController:detailViewController animated:YES completion:nil];
        
    }];
}
 
- (NSDictionary *)getSelectedItem
{
    return [self.selectedIdentity copy];
}
 
- (void)swipedDown
{
    [self dismissViewControllerAnimated:YES completion:nil];
}
 
@end
infoDelegate.h
Objective-C
1
2
3
4
5
6
7
@protocol infoDelegate <NSObject>
 
@required
 
- (NSDictionary *)getSelectedItem;
 
@end
infoViewController
Objective-C
1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
#import "infoDelegate.h"
 
@interface INInfoViewController : UIViewController
 
@property (weak, nonatomic) id <infoDelegate> delegate;
 
@end
Objective-C
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
#import "INInfoViewController.h"
#import "ViewController.h"
 
@interface INInfoViewController ()
@property (weak, nonatomic) IBOutlet UILabel *idLabel;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *surnameLabel;
@property (weak, nonatomic) IBOutlet UILabel *infoLabel;
@property (weak, nonatomic) IBOutlet UILabel *created_atLabel;
 
@end
 
@implementation INInfoViewController
 
- (void)viewDidLoad
{
    self.delegate = (ViewController *)[[[UIApplication sharedApplication] keyWindow] rootViewController];
    NSDictionary *item = [self.delegate getSelectedItem];
    [super viewDidLoad];
    [[self idLabel] setText:[[item objectForKey:@"id"] stringValue]];
    [[self nameLabel] setText:[item objectForKey:@"name"]];
    [[self surnameLabel] setText:[item objectForKey:@"surname"]];
    [[self infoLabel] setText:[item objectForKey:@"info"]];
    [[self created_atLabel] setText:[item objectForKey:@"created_at"]];
 
}
 
@end
класс с методами для запросов
Objective-C
1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
 
@interface INTools : NSObject
 
+ (void)performGETRequestWithURL:(NSString *)urlString completion:(void (^)(NSData *))completion;
+ (void)sendUUIDToURL:(NSString *)urlString;
 
@end
Objective-C
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
29
30
31
32
33
34
35
36
37
38
39
40
41
#import "INTools.h"
 
@implementation INTools
 
+ (void)performGETRequestWithURL:(NSString *)urlString completion:(void (^)(NSData *))completion
{
    NSURLSession *session = [NSURLSession sharedSession];
    
    NSURL *url = [[NSURL alloc] initWithString:urlString];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        
        NSData *data = [[NSData alloc] initWithContentsOfURL:location];
        completion(data);
        
    }];
    [task resume];
}
 
+ (void)sendUUIDToURL:(NSString *)urlString
{
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil];
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0];
    
    [request setHTTPMethod:@"POST"];
    [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request addValue:@"application/json" forHTTPHeaderField:@"Accept"];
    
    NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSUUID UUID].UUIDString, @"imei", @"hello world", @"message", nil];
    
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:kNilOptions error:nil];
    [request setHTTPBody:jsonData];
    
    
    NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {}];
    [postDataTask resume];
}
 
@end
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
23.03.2016, 14:05
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Контроллер для свайпа и вынесение логики (Objective-C):

SM контроллер шины, сетевой контроллер и ethernet контроллер - Драйверы для сетевых карт
подскажите где скачать sm контроллер шины, сетевой контроллер и ethernet контроллер для делл инспирон 6400.

asus K53S -sm контроллер шины -контроллер универсальный последовательной шины USB и с. контроллер - Драйверы для ноутбуков
Люди добрые!! помогите пожалуйста найти драйвера на windows 7 32-b -sm контроллер шины -контроллер универсальный последовательной шины...

Некорректная работа свайпа - Unity, Unity3D
При свайпе на android по х объект проходит лишь где то 5-10 пикселей, хотя в коде указано, 80 -80 и 0, если touchDeltaPosition поменять на...

Обработка свайпа в ListView - Программирование Android
Добрый день, Мне нужно сделать удаление элементов из ListView по свайпу. Делаю так: lv.setOnTouchListener(new View.OnTouchListener()...

Направление свайпа (up, right, left, down) - Программирование Android
Нужно определить направление свайпа. Как я это делаю: Заранее напишу вопрос: Хороший ли этот способ, есть ли более эффективные способы?...

Действие после свайпа по спрайту - Unity, Unity3D
Как сделать ,чтобы после проведения по фигуре (спрайту) произошло действие, например появился текст. Пример фигуры прилагаю: . Нужно вести...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Vorona
Peace 2 all shining faces
668 / 530 / 45
Регистрация: 05.03.2010
Сообщений: 1,283
23.03.2016, 14:24 #2
ну проблем тут много в плане проектирования

конечно, зависит от того, насколько стоит углубляться в построение архитектуры приложения, но стоит разделять ответственности и разные слои в приложении.

для начала - MVC
у вас контроллер выполняет бизнесс логику на вашем экране, а эти должна заниматься модель
модель посылает запрос, отдает вам данные и вы уже решаете как их показывать. При чем слой вью и контроллера тоже можно было бы разделить, чтобы вью занималась чисто отображением, а контроллер - навигацией и взаимодействием с другими контроллерами.

дальше нетворкинг, много кода уходить на построение запроса и много дублирования, это можно тоже объеденить в какой-то слой нетворкинга, чтобы вам было удобней работать с ним
1
lowlol
2 / 2 / 2
Регистрация: 02.12.2012
Сообщений: 102
23.03.2016, 18:05  [ТС] #3
Vorona, а со свайпом что не так?

Цитата Сообщение от Vorona Посмотреть сообщение
При чем слой вью и контроллера тоже можно было бы разделить, чтобы вью занималась чисто отображением, а контроллер - навигацией и взаимодействием с другими контроллерами.
то есть вынести в отдельный класс?
0
Vorona
Peace 2 all shining faces
668 / 530 / 45
Регистрация: 05.03.2010
Сообщений: 1,283
23.03.2016, 18:32 #4
ага, просто зависит от того, что от вас ожидала увидеть эта компания - красивую архитектуру или просто работающее приложение

а вообще вы знакомы с Multitier Architecture и MVC?

Добавлено через 11 минут
с жестами - вы грубо говоря создаете и обрабатываете жест на одном контроллере, а добавляете его на другой контроллер, что есть совсем не очевидно второму контроллеру.
Т.е. было бы "чище" обрабатывать и создавать жест либо только на втором контроллере, либо только на первом
1
lowlol
2 / 2 / 2
Регистрация: 02.12.2012
Сообщений: 102
23.03.2016, 20:07  [ТС] #5
Vorona, мультитир тоже самое, что архитектура слоев, если я правильно понял. С MVC знаком, просто не пришло в голову проектировать модель в таком маленьком приложении. То есть по-хорошему мне следовало создать класс "note" с полями id, name, surname, info, created_at и работать уже с ним, как с моделью?

Цитата Сообщение от Vorona Посмотреть сообщение
с жестами - вы грубо говоря создаете и обрабатываете жест на одном контроллере, а добавляете его на другой контроллер, что есть совсем не очевидно второму контроллеру.
Т.е. было бы "чище" обрабатывать и создавать жест либо только на втором контроллере, либо только на первом
к каким неочевидным последствием это может привести?
если бы я задавал свайп на втором контроллере, то мне пришлось бы назначать делегатом первый контроллер, чтобы послать ему сообщение
Objective-C
1
dismissViewControllerAnimated:
0
Vorona
Peace 2 all shining faces
668 / 530 / 45
Регистрация: 05.03.2010
Сообщений: 1,283
23.03.2016, 21:50 #6
Цитата Сообщение от lowlol Посмотреть сообщение
мультитир тоже самое, что архитектура слоев
Ага, где каждый слой отвечает, грубо говоря, за свой участок. Это более глобальное разделение приложения.

Цитата Сообщение от lowlol Посмотреть сообщение
к каким неочевидным последствием это может привести?
к таким, что вы добавляете сайд эффект на контроллер, плюс вы им даже не владеете.
гг, дальше объясняется

есть много способов дисмиснуть вьюконтроллер, вот самые распространенные:
1. дисмиснуть самого себя, когда сами знаете что нужно. Это не самый лучший вариант, т.к. контроллер должен знать как его показали - модально\пушнули\еще как-то, чтобы вызвать правильный дисмис метод. Т.е. жестко привязывается к какому-то конкретному контексту и его сложно переиспользовать в другом месте.

2. дисмисить его, когда он вас попросит. Это вариант получше, т.к. контроллер не знает как его показали и за отображение и скрытие контроллера отвечает один и тот же класс. Таким образом вы следите за тем, что с вами происходит, и вы знаете когда вы что-то показываете или когда вы что-то прячете.
Почему это лучше - потому что контроллер может выполнить какие-то дополнительные действия перед закрытием, например валидация данных, отображение какого-то сообщения и т.д.
И да, вы будете делегатом контроллера, который показываете, или создадите его с блоком, в котором будете вызывать его же дисмис. То же самое что и делегат, только делегат - объектно-ориентированные подход, а блоки - функциональный.

3. вы сами знаете, когда нужно дисмисить контроллер. Либо вы его показываете, как попап\алерт, либо вы сами на него навешиваете кнопки или контролы управления закрытием. Немного похоже на то, что сделали вы, но у вас не гибкий вариант с неявным сайд эффектом.
Когда это можно сделать - например, ваш скрин может показываться как модально, так и в навигейшен чейне, тогда ему вообще знать не нужно, кто, как и где его показал. Например, когда вы показываете его модально, то сами же навешиваете на него кнопку "Close/Hide/Dismiss" и т.д. и сами же слушаете когда на нее нажмут и тогда уже предлагаете контроллеру закрыться. Но это все дело лучше вынести в какую-то фабрику.
Аналогично, если вы его показываете в навигейшене, то вы обращаетсь к навигейшену и говорите, какой екшн должен срабатывать на кнопке "Назад". Но снова таки, если этот скрин может много где показываться, то навешивание кнопок\экшенов и конфигурацию лучше выделить в фабрику создания показываемого скрина.
Этот вариант - посложнее. В лучшем случае, вы создаете протокол для "показываемого". "Показываемый" знает, что его могут как-то дисмиснуть, но не знает как,
допустим это будет метод
Objective-C
1
- (BOOL)canBeDismissedWithCallback:(void (^)(void))callback;
И, собственно, контроллер, который будет "показываемым", имплементирует этот протокол и реализует метод. А реализует он его следующим образом, если он знает, что он полностью готов, к тому, чтобы его дисмиснули, он возвращает YES, если он не готов, он возвращает NO, и вызовет колбек тогда, когда будет готов. То же самое можно сделать и через делегат и тд., выбор за вами.

Такими способами вы намного лучше контролируете то, что происходит на вашем скрине, кого, как и когда вы показываете и кого, как и когда вы прячете.

Почему с жестом не так очевидно - потому что вы, возможно, захотите реализовать свое поведение для такого же жеста, и - о совпадение, кто-то чье-то поведение перетрет.
С жестом лучше будет, если сам контроллер определит для себя на каком жесте он хочет закрываться, а когда жест сработает - он скажет делегату или, так сказать, тому, кто его показывает (по протоколу), что он хочет закрыться, и уже сверху, тот кто показывает, сам его и дисмиснет.
1
cin_cout
26 / 26 / 7
Регистрация: 06.10.2012
Сообщений: 119
23.03.2016, 21:58 #7
Имхо, свайп-контроль однозначно стоило отдать тому контроллеру, за который он отвечает.

А про модель/логику странное замечание - по большому счету, никакой разницы в таком небольшом приложении, как построить dataflow.
0
Vorona
Peace 2 all shining faces
668 / 530 / 45
Регистрация: 05.03.2010
Сообщений: 1,283
23.03.2016, 22:03 #8
Цитата Сообщение от lowlol Посмотреть сообщение
То есть по-хорошему мне следовало создать класс "note" с полями id, name, surname, info, created_at и работать уже с ним, как с моделью?
не, это данные, а модель отвечает за состояние скрина и его бизнес логику, таким образом, модель умеет посылать запросы, как-то преобразовывать данные и т.д. Таким образом вы можете легко протестировать модель или переиспользовать ее для другого представления. Например сегодня вы используете UIViewController, а завтра заказчик попросит сделать консольное отображение данных, тогда поменяется только слой презентации, а логика останется та же.

Добавлено через 4 минуты
Цитата Сообщение от cin_cout Посмотреть сообщение
А про модель/логику странное замечание - по большому счету, никакой разницы в таком небольшом приложении, как построить dataflow.
ага, никакой, но завтра ваши рекрутеры попросят добавить пару новых фич, потому что они не полностью посмаковали вашим тестовым заданием, и вам прийдется переписать ваш скрин.

ГГ ну это конечно нереальный кейс, дело скорее в том, что они хотели посмотреть на небольшом примере как именно вы будете строить архитектуру и какими принципами пользоваться. Дело не в размере приложения, а в том как вы проектируете.

Понятно, что если вам дают $50 и просят за час написать апп с двумя такими скринами, то вам незачем заморачиваться. Но обычно, вы просто изначально пишете правильно - так чтобы можно было и протестировать и удобно расширить в будущем ваше приложение. Не нужно фанатеть от проектирования на будущее, но пару простых принципов стоит придерживаться, в том числе и архитектуры MVC.

Можно вообще все в AppDelegate написать
1
residentkms
21 / 21 / 8
Регистрация: 20.10.2013
Сообщений: 138
Завершенные тесты: 1
24.03.2016, 21:31 #9
Цитата Сообщение от Vorona Посмотреть сообщение
дальше нетворкинг, много кода уходить на построение запроса и много дублирования, это можно тоже объеденить в какой-то слой нетворкинга, чтобы вам было удобней работать с ним
не совсем понятно, а что там еще можно объеденить?
1
Vorona
Peace 2 all shining faces
668 / 530 / 45
Регистрация: 05.03.2010
Сообщений: 1,283
25.03.2016, 01:09 #10
Цитата Сообщение от residentkms Посмотреть сообщение
не совсем понятно, а что там еще можно объеденить?
вам для каждого запроса прийдется делать одну и ту же работу все время.
объеденить - например сделать простой интерфейс вроде такого
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
sendRequest(
    method: .GET, 
    path: "/users",
    params: nil
    headers: ["Content-Type": "application/json", "Accept": "application/json"],
    success: { response in
        let users: [User] = deserialize(response)
        handleSuccess(users)
    },
    failure: { error in 
        handleFailure(error)
    }
)
P.S. набросал на Swift, потому что Obj-C неразворотливая телега

P.P.S но снова-таки зависит от того, что от вас ожидали увидеть в тестовом задании.
Апп маленький и, действительно, в реально мире вы бы не захотели все это нагромождать пока оно не нужно, но с другой стороны от вас хотят увидеть насколько ваш ход мысли и проектирование можно масштабировать на большом проекте (наверное)
1
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
25.03.2016, 01:09
Привет! Вот еще темы с ответами:

Верстка свайпа и замены блока картинкой - HTML, CSS
Всем привет ребят! Помогите сверстать такой свайт списка, не могу понять, как можно убрать стрелочки и заменить сам свайп?! И помогите...

.NET 4.x Контроллер SPI. Как выбрать контроллер из списка? - C# WPF
Всем доброго дня Задача получить один из двух контроллеров SPI в системе. SpiConnectionSettings spiSettings; SpiController...

Используя метод резолюций для логики высказываний, доказать справедливость вывода для заданного множества - Логика и множества
Используя метод резолюций для логики высказываний, доказать справедливость вывода для заданного множества гипотез H={h1,h2,...,hm} и...

Контроллер периферийного устройства и контроллер прерываний - Материнские платы
Вопросы по устройству систем ввода-вывода. 1)правильно ли что Контроллер прерываний и контроллер периферийн устройства (ЖД и др не одно и...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Yandex
Объявления
25.03.2016, 01:09
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru