NSString
's AppDelegate
' s if (A and (B or C)) then use Pattern_N;
Any pattern describes a task that arises again and again in our work, as well as the principle of its solution, and in such a way that this solution can then be used a million times without reinventing anything.
Although Alexander was referring to the patterns that arise in the design of buildings and cities, but his words are true in relation to the patterns of object-oriented design. Our solutions are expressed in terms of objects and interfaces, not walls and doors, but in both cases the meaning of the pattern is to offer a solution to a specific task in a specific context.
Album
, and it will be a subclass of NSObject
.@interface
and @end
: @property (nonatomic, copy, readonly) NSString * title; @property (nonatomic, copy, readonly) NSString * artist; @property (nonatomic, copy, readonly) NSString * genre; @property (nonatomic, copy, readonly) NSString * coverUrl; @property (nonatomic, copy, readonly) NSString * year; - (id)initWithTitle:(NSString *)title artist:(NSString *)artist coverUrl:(NSString *)coverUrl year:(NSString *)year;
readonly
flag, since we do not need to change them after creating the Album
object.@implementation
and @end
: - (id)initWithTitle:(NSString *)title artist:(NSString *)artist coverUrl:(NSString *)coverUrl year:(NSString *)year { self = [super init]; if (self) { _title = title; _artist = artist; _coverUrl = coverUrl; _year = year; _genre = @"Pop"; } return self; }
AlbumView
class - a subclass of UIView
.@interface
and @end
: - (id)initWithFrame:(CGRect)frame albumCover:(NSString *)albumCover;
@implementation
and @end
with this one: @implementation AlbumView { UIImageView * coverImage; UIActivityIndicatorView * indicator; } - (id)initWithFrame:(CGRect)frame albumCover:(NSString *)albumCover { self = [super initWithFrame:frame]; if (self) { // : self.backgroundColor = [UIColor blackColor]; // - 5 : coverImage = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, frame.size.width-10, frame.size.height-10)]; [self addSubview:coverImage]; // : indicator = [[UIActivityIndicatorView alloc] init]; indicator.center = self.center; indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; [indicator startAnimating]; [self addSubview:indicator]; } return self; } @end
coverImage
. This variable is an image from a cover album. The second variable indicator
is an indicator that spins, depicting activity while the cover is loading.*.m
) and not in the header file ( *.h
)? Because other classes (outside AlbumView
) do not need to know about the existence of these variables, since they are used only inside the class. This moment is extremely important if you create a library (or framework) for other developers.Album
.AlbumView
.ViewController
.AlbumView
to display movie and book objects! And if we want to create a completely different project that will deal with albums, we can use the Album
class in it - a model that does not depend on the presentation. This is the power of MVC!Album
and AlbumView
.ViewController
's play two or three roles at the same time. - Approx. per.Model
, View
and Controller
:Model
. Do the same for Controller
and View
.Model
group.View
group.Controller
group.[NSUserDefaults standardUserDefaults]
, [UIApplication sharedApplication]
, [UIScreen mainScreen]
, [NSFileManager defaultManager]
- each of these methods returns a singleton object.Logger
(only if you do not write several different logs at the same time). Or the global configuration reference class: it is much better to provide thread-safe access to a certain shared resource (for example, to the settings file) than to have many classes that modify the settings file, possibly at the same time.Logger
class has one instance
property (a pointer to a single instance) and two methods: sharedInstance()
and init()
.sharedInstance()
method, the instance
property has not yet been initialized, then a new instance of the class is created and a pointer to it is returned.sharedInstance()
, we are immediately returned to the instance
without initialization. Such a scheme guarantees the existence of only one instance for the entire duration of the program.API
. There we will add classes that provide services for our application. In this group, create a class from the template iOS> Cocoa Touch> Objective-C class , a subclass of NSObject
, and name it LibraryAPI
. @interface LibraryAPI : NSObject + (LibraryAPI *)sharedInstance; @end
@implentation
line: + (LibraryAPI *)sharedInstance { // 1 static LibraryAPI * _sharedInstance = nil; // 2 static dispatch_once_t oncePredicate; // 3 dispatch_once(&oncePredicate, ^{ _sharedInstance = [[LibraryAPI alloc] init]; }); return _sharedInstance; }
LibraryAPI
instance. This is the essence of the “loner” pattern: the initialization block will never be executed again.sharedInstance()
again, the code inside the dispatch_once
block will not be executed (because it has already been executed before), and you will receive a pointer to the previous created instance of the LibraryAPI
.API
group, create a new class: iOS> Cocoa Touch> Objective-C class , a subclass of NSObject
, and call it PersistencyManager
. #import "Album.h"
@interface
: - (NSArray *)albums; - (void)addAlbum:(Album *)album atIndex:(NSUInteger)index; - (void)deleteAlbumAtIndex:(NSUInteger)index;
@implementation
line: @interface PersistencyManager () { NSMutableArray * albums; // } @end
PersistencyManager
class after the @implementation
line: - (id)init { self = [super init]; if (self) { albums = [NSMutableArray arrayWithArray: @[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_david_bowie_best_of_bowie.png" year:@"1992"], [[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_no_doubt_its_my_life_bathwater.png" year:@"2003"], [[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_sting_nothing_like_the_sun.png" year:@"1999"], [[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_u2_staring_at_the_sun.png" year:@"2000"], [[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_madonna_american_pie.png" year:@"2000"]]]; } return self; }
init
method, we collect an array of five albums for example (if you have nothing to do, you can replace them with your favorite music). - (NSArray *)albums { return albums; } - (void)addAlbum:(Album *)album atIndex:(NSUInteger)index { if (albums.count >= index) [albums insertObject:album atIndex:index]; else [albums addObject:album]; } - (void)deleteAlbumAtIndex:(NSUInteger)index { [albums removeObjectAtIndex:index]; }
PersistencyManager
do in the singleton chapter? The relationship between LibraryAPI
and PersistencyManager
will be shown in the next chapter.PersistencyManager
for local data storage and HTTPClient
for network interaction.Other classes in your project should not think about these two things.LibraryAPI
- must refer to instances PersistencyManager
and HTTPClient
. LibraryAPI
- this is the facade that provides a simple interface for accessing services (storage and transmission of data).LibraryAPI
, HTTPClient
PersistencyManager
. #import "Album.h"
- (NSArray *)albums; - (void)addAlbum:(Album *)album atIndex:(int)index; - (void)deleteAlbumAtIndex:(int)index;
#import "PersistencyManager.h" #import "HTTPClient.h"
@implementation
): @interface LibraryAPI () { PersistencyManager * persistencyManager; HTTPClient * httpClient; BOOL isOnline; } @end
isOnline
: , (, ) ? - (id)init { self = [super init]; if (self) { persistencyManager = [[PersistencyManager alloc] init]; httpClient = [[HTTPClient alloc] init]; isOnline = NO; } return self; }
isOnline
NO
.LibraryAPI
: - (NSArray *)albums { return [persistencyManager albums]; } - (void)addAlbum:(Album *)album atIndex:(int)index { [persistencyManager addAlbum:album atIndex:index]; if (isOnline) { [httpClient postRequest:@"/api/addAlbum" body:[album description]]; } } - (void)deleteAlbumAtIndex:(int)index { [persistencyManager deleteAlbumAtIndex:index]; if (isOnline) { [httpClient postRequest:@"/api/deleteAlbum" body:[@(index) description]]; } }
addAlbum:atIndex:
. , , , . «»: - Album
, :Album
— «», , . ( Album
) , Album
, Album
.Album
. , UITableView
.TableRepresentation
Album
Album+TableRepresentation
, Album
. , ( - ). - (NSDictionary *)tr_tableRepresentation;
tr_
— ( TableRepresentation
). , . - (NSDictionary *)tr_tableRepresentation { return @{@"titles":@[@"", @"", @"", @""], @"values":@[self.artist, self.title, self.genre, self.year]}; }
Album
.Album
without inheritance. (If you want to inherit from Album, this can also be done.)UITableView
“smart view Album
” without modifying the code itself Album
.@interface NSString
— : NSStringExtensionMethods
, NSExtendedStringPropertyListParsing
, NSStringDeprecated
. , .UITableView
, , tableView:numberOfRowsInSection:
( ).UITableView
, . , . UITableView
« » . : UITableView
.UITableView
:UITableView
, . , . ( ), . , , ( ).UITableView
, UITextView
, UITextField
, UIWebView
, UIAlert
, UIActionSheet
, UICollectionView
, UIPickerView
, UIGestureRecognizer
, UIScrollView
, … , . #import "LibraryAPI.h" #import "Album+TableRepresentation.h"
@interface
@end
: @interface ViewController () { UITableView * dataTable; NSArray * allAlbums; NSDictionary * currentAlbumData; int currentAlbumIndex; } @end
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
UITableViewDataSource
, UITableViewDelegate
— . , . «» .UITableView
, .viewDidLoad
: - (void)viewDidLoad { [super viewDidLoad]; // 1 self.view.backgroundColor = [UIColor colorWithRed:0.76f green:0.81f blue:0.87f alpha:1.f]; currentAlbumIndex = 0; // 2 allAlbums = [[LibraryAPI sharedInstance] albums]; // 3 // UITableView, CGRect frame = CGRectMake(0.f, 120.f, self.view.frame.size.width, self.view.frame.size.height - 120.f); dataTable = [[UITableView alloc] initWithFrame:frame style:UITableViewStyleGrouped]; dataTable.delegate = self; dataTable.dataSource = self; dataTable.backgroundView = nil; [self.view addSubview:dataTable]; }
PersistencyManager
!UITableView
. , ViewController
— (data source) UITableView
, , UITableView
, ViewController
'. - (void)showDataForAlbumAtIndex:(int)albumIndex { // : , if (albumIndex < allAlbums.count) { // : Album * album = allAlbums[albumIndex]; // , TableView: currentAlbumData = [album tr_tableRepresentation]; } else { currentAlbumData = nil; } // , . TableView [dataTable reloadData]; }
showDataForAlbumAtIndex:
. , reloadData
. , UITableView
, , , , , .viewDidLoad
: [self showDataForAlbumAtIndex:currentAlbumIndex];
currentAlbumIndex
was previously set to 0
, we see the ViewController
delegate and data source UITableView
. But!Having done this, we must implement all the required methods (including tableView:numberOfRowsInSection:
), but we have not done it yet.@implementation
and @end
: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [currentAlbumData[@"titles"] count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"]; } cell.textLabel.text = currentAlbumData[@"titles"][indexPath.row]; cell.detailTextLabel.text = currentAlbumData[@"values"][indexPath.row]; return cell; }
tableView:numberOfRowsInSection:
, TableView
, .tableView:cellForRowAtIndexPath:
( title
) ( value
).UITableView
. .UITableViewDelegate
, UIScrollViewDelegate
, NSCoding
, NSCopying
. , NSCopying
copy
.HorizontalScroller
UIView
.@end
: @protocol HorizontalScrollerDelegate <NSObject> // @end
HorizontalScrollerDelegate
— NSObject
( , ). — NSObject
, / NSObject
. , NSObject
, HorizontalScroller
. , .@protocol
@end
: @required // , - (NSInteger)numberOfViewsForHorizontalScroller:(HorizontalScroller *)scroller; // <index> - (UIView *)horizontalScroller:(HorizontalScroller *)scroller viewAtIndex:(int)index; // <index> - (void)horizontalScroller:(HorizontalScroller *)scroller clickedViewAtIndex:(int)index; @optional // , // ( , 0, ) - (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller *)scroller;
HorizontalScroller
.HorizontalScroller
. , .. HorizontalScroller
. What to do?HorizontalScrollerDelegate
. So that the compiler knows that we have such a protocol (but will be announced later). Add above line @interface
: @protocol HorizontalScrollerDelegate;
@interface
and @end
: @property (weak) id<HorizontalScrollerDelegate> delegate; - (void)reload;
weak
(«») ( ) delegate
. , «retain-». , , . .id<HorizontalScrollerDelegate>
, , HorizontalScrollerDelegate
( ).reload
reloadData
UITableView
: , . #import "HorizontalScroller.h" // 1 #define VIEW_PADDING 10 #define VIEW_DIMENSIONS 100 #define VIEWS_OFFSET 100 // 2 @interface HorizontalScroller () <UIScrollViewDelegate> @end // 3 @implementation HorizontalScroller { UIScrollView * scroller; } @end
- (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; scroller.delegate = self; [self addSubview:scroller]; UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollerTapped:)]; [scroller addGestureRecognizer:tapRecognizer]; } return self; }
scroller
) HorizontalScroller
. UITapGestureRecognizer
ScrollView , . , HorizontalScroller
. - (void)scrollerTapped:(UITapGestureRecognizer *)gesture { CGPoint location = [gesture locationInView:gesture.view]; // enumerator, .. . // subviews, : for (int index = 0; index < [self.delegate numberOfViewsForHorizontalScroller:self]; index++) { UIView * view = scroller.subviews[index]; if (CGRectContainsPoint(view.frame, location)) { [self.delegate horizontalScroller:self clickedViewAtIndex:index]; CGPoint offset = CGPointMake(view.frame.origin.x - self.frame.size.width/2 + view.frame.size.width/2, 0); [scroller setContentOffset:offset animated:YES]; break; } } }
gesture
), , ( locationInView:
).numberOfViewsForHorizontalScroller:
. HorizontalScroller
, , ( HorizontalScrollerDelegate
).CGRectContainsPoint
. , horizontalScroller:clickedViewAtIndex:
. - (void)reload { // 1 - , : if (self.delegate == nil) return; // 2 - subviews: [scroller.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) { [obj removeFromSuperview]; }]; // 3 - xValue - : CGFloat xValue = VIEWS_OFFSET; for (int i = 0; i < [self.delegate numberOfViewsForHorizontalScroller:self]; i++) { // 4 - : xValue += VIEW_PADDING; UIView * view = [self.delegate horizontalScroller:self viewAtIndex:i]; view.frame = CGRectMake(xValue, VIEW_PADDING, VIEW_DIMENSIONS, VIEW_DIMENSIONS); [scroller addSubview:view]; xValue += VIEW_DIMENSIONS + VIEW_PADDING; } // 5 [scroller setContentSize:CGSizeMake(xValue + VIEWS_OFFSET, self.frame.size.height)]; // 6 - initialView, : if ([self.delegate respondsToSelector:@selector(initialViewIndexForHorizontalScroller:)]) { int initialView = [self.delegate initialViewIndexForHorizontalScroller:self]; CGPoint offset = CGPointMake(initialView * (VIEW_DIMENSIONS + (2 * VIEW_PADDING)), 0); [scroller setContentOffset:offset animated:YES]; } }
VIEWS_OFFSET
). 100
, #define
.HorizontalScroller
( UIView
) , .HorizontalScroller
Looks at whether the delegate responds to the message selector initialViewIndexForHorizontalScroller:
. This check is necessary because this protocol method is optional. If the delegate does not implement this method, the default value is taken 0
. This part of the code sets the scrolling view to the center of the view defined by the delegate ( initialView
).reload
in the event that our data has changed. Also this method needs to be called when we add HorizontalScroller
to a new view. To do this, add this method to the class HorizontalScroller
: - (void)didMoveToSuperview { [self reload]; }
didMoveToSuperview
, , : , - . .HorizontalScroller
— , scroll view. , . - (void)centerCurrentView { int xFinal = scroller.contentOffset.x + (VIEWS_OFFSET / 2) + VIEW_PADDING; int viewIndex = xFinal / (VIEW_DIMENSIONS + (2 * VIEW_PADDING)); xFinal = viewIndex * (VIEW_DIMENSIONS + (2 * VIEW_PADDING)); [scroller setContentOffset:CGPointMake(xFinal, 0) animated:YES]; [self.delegate horizontalScroller:self clickedViewAtIndex:viewIndex]; }
UIScrollViewDelegate
: - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self centerCurrentView]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self centerCurrentView]; }
scrollViewDidEndDragging:willDecelerate:
, . decelerate
(«») true
, , « ». , scrollViewDidEndDecelerating
. ( centerCurrentView
), , , .HorizontalScroller
! . Album
AlbumView
. Excellent!This means our new scroller has turned out really independent of the content, and it can be reused. #import "HorizontalScroller.h" #import "AlbumView.h"
HorizontalScrollerDelegate
to the list of protocols that implements ViewController
: @interface ViewController () <UITableViewDataSource, UITableViewDelegate, HorizontalScrollerDelegate>
HorizontalScroller * scroller;
#pragma mark - HorizontalScrollerDelegate methods - (void)horizontalScroller:(HorizontalScroller *)scroller clickedViewAtIndex:(int)index { currentAlbumIndex = index; [self showDataForAlbumAtIndex:index]; }
#pragma mark
. , IDE — . , AppCode ⌘F12 : - (NSInteger)numberOfViewsForHorizontalScroller:(HorizontalScroller *)scroller { return allAlbums.count; }
- (UIView *)horizontalScroller:(HorizontalScroller *)scroller viewAtIndex:(int)index { Album * album = allAlbums[index]; return [[AlbumView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f) albumCover:album.coverUrl]; }
AlbumView
and transfer it to HorizontalScroller
. - (void)reloadScroller { allAlbums = [[LibraryAPI sharedInstance] albums]; if (currentAlbumIndex < 0) currentAlbumIndex = 0; else if (currentAlbumIndex >= allAlbums.count) currentAlbumIndex = allAlbums.count - 1; [scroller reload]; [self showDataForAlbumAtIndex:currentAlbumIndex]; }
LibraryAPI
, and then sets the current view. Just in case - check for going beyond the array.viewDidLoad
front of the line[self showDataForAlbumAtIndex:currentAlbumIndex];
scroller = [[HorizontalScroller alloc] initWithFrame:CGRectMake(0.f, 20.f, self.view.frame.size.width, 120.f)]; scroller.backgroundColor = [UIColor colorWithRed:0.24f green:0.35f blue:0.49f alpha:1]; scroller.delegate = self; [self.view addSubview:scroller]; [self reloadScroller];
HorizontalScroller
, sets the background color, designates itself as a delegate. Then it adds a scroller to the main screen and updates the data in it.UITableViewDelegate
and UITableViewDataSource
. Both of them are protocols UITableView
, i.e. technically could exist in the same protocol, but broken up for convenience. Try to develop protocols so that everyone is responsible for their own functional area.LibraryAPI
, the new method will have to go there. But first you need to make out a few points:AlbumView
should not work directly with LibraryAPI
. We don't want to mix UI code with network interaction, right?LibraryAPI
should not know about AlbumView
.LibraryAPI
must report AlbumView
as soon as the covers are loaded so that AlbumView
they are displayed.UIKeyboardWillShowNotification
UIKeyboardWillHideNotification
, . , UIApplicationDidEnterBackgroundNotification
. #import <UIKit/UIApplication.h>
UIApplication.h
.initWithFrame:albumCover:
after[self addSubview:indicator];
[[NSNotificationCenter defaultCenter] postNotificationName:@"BLDownloadImageNotification" object:self userInfo:@{@"coverUrl":albumCover, @"imageView":coverImage}];
NSNotificationCenter
. The notification contains the URL of the image you want to upload, and UIImageView
where you want to put this image. That's all that our “subscriber” needs to know, who will receive this notification in order to complete the download task.init
immediately after isOnline = NO
: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadImage:) name:@"BLDownloadImageNotification" object:nil];
AlbumView
"BLDownloadImageNotification"
, .. LibraryAPI
, LibraryAPI
. , LibraryAPI
downloadImage:
.BlueLibrary
. - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
- (void)saveImage:(UIImage *)image filename:(NSString *)filename; - (UIImage *)getImage:(NSString *)filename;
- (void)saveImage:(UIImage *)image filename:(NSString *)filename { filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename]; NSData * data = UIImagePNGRepresentation(image); [data writeToFile:filename atomically:YES]; } - (UIImage *)getImage:(NSString *)filename { filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename]; NSData * data = [NSData dataWithContentsOfFile:filename]; return [UIImage imageWithData:data]; }
getImage:
nil
, . ( , , UIImage
, . — . . ) - (void)downloadImage:(NSNotification *)notification { // 1 NSString * coverUrl = notification.userInfo[@"coverUrl"]; UIImageView * imageView = notification.userInfo[@"imageView"]; // 2 imageView.image = [persistencyManager getImage:[coverUrl lastPathComponent]]; if (imageView.image == nil) { // 3 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage * image = [httpClient downloadImage:coverUrl]; // 4 dispatch_sync(dispatch_get_main_queue(), ^{ imageView.image = image; [persistencyManager saveImage:image filename:[coverUrl lastPathComponent]]; }); }); } }
downloadImage
NSNotification
. URL UIImageView
.PersistencyManager
, .HTTPClient
.UIImageView
.HorizontalScroller
:UIImageView
.initWithFrame:albumCover:
[self addSubview:indicator];
[coverImage addObserver:self forKeyPath:@"image" options:0 context:nil];
self
( AlbumView
) image
coverImage
.@end
: - (void)dealloc { [coverImage removeObserver:self forKeyPath:@"image"]; }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"image"]) { [indicator stopAnimating]; } }
image
. , , .change
, . , . nil
, => . — . . )dealloc
. , ! - (void)saveCurrentState { // , // , . // . , // NSUserDefaults: [[NSUserDefaults standardUserDefaults] setInteger:currentAlbumIndex forKey:@"currentAlbumIndex"]; } - (void)loadPreviousState { currentAlbumIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"currentAlbumIndex"]; [self showDataForAlbumAtIndex:currentAlbumIndex]; }
saveCurrentState
. NSUserDefaults
— , iOS.loadPreviousState
, . «», .viewDidLoad
: [self loadPreviousState];
UIApplicationDidEnterBackgroundNotification
, . , saveCurrentState
. Conveniently? Yes.viewDidLoad
: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCurrentState) name:UIApplicationDidEnterBackgroundNotification object:nil];
ViewController
, saveCurrentState
. - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
ViewController
, .TableView
, . Why?initialViewIndexForHorizontalScroller:
— . ( ViewController
). . - (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller *)scroller { return currentAlbumIndex; }
currentAlbumIndex
. .PersistencyManager
init
, , Album
. , PersistencyManager
. . ?Album
. , :Album
. , NSCoding
. Album.h @interface
: @interface Album : NSObject <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.year forKey:@"year"]; [aCoder encodeObject:self.title forKey:@"album"]; [aCoder encodeObject:self.artist forKey:@"artist"]; [aCoder encodeObject:self.coverUrl forKey:@"cover_url"]; [aCoder encodeObject:self.genre forKey:@"genre"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { _year = [aDecoder decodeObjectForKey:@"year"]; _title = [aDecoder decodeObjectForKey:@"album"]; _artist = [aDecoder decodeObjectForKey:@"artist"]; _coverUrl = [aDecoder decodeObjectForKey:@"cover_url"]; _genre = [aDecoder decodeObjectForKey:@"genre"]; } return self; }
encodeWithCoder:
. initWithCoder:
. , .Album
, , / . - (void)saveAlbums;
- (void)saveAlbums { NSString * filename = [NSHomeDirectory() stringByAppendingString:@"/Documents/albums.bin"]; NSData * data = [NSKeyedArchiver archivedDataWithRootObject:albums]; [data writeToFile:filename atomically:YES]; }
NSKeyedArchiver
" albums.bin
".albums
( Album
). NSArray
Album
NSCoding
, .init
: - (id)init { self = [super init]; if (self) { NSData * data = [NSData dataWithContentsOfFile:[NSHomeDirectory() stringByAppendingString:@"/Documents/albums.bin"]]; albums = [NSKeyedUnarchiver unarchiveObjectWithData:data]; if (albums == nil) { albums = [NSMutableArray arrayWithArray: @[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_david_bowie_best_of_bowie.png" year:@"1992"], [[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_no_doubt_its_my_life_bathwater.png" year:@"2003"], [[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_sting_nothing_like_the_sun.png" year:@"1999"], [[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_u2_staring_at_the_sun.png" year:@"2000"], [[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_madonna_american_pie.png" year:@"2000"]]]; [self saveAlbums]; } } return self; }
NSKeyedUnarchiver
, . , . , . - (void)saveAlbums;
LibraryAPI
, , . PersistencyManager
', . - (void)saveAlbums { [persistencyManager saveAlbums]; }
saveCurrentState
: [[LibraryAPI sharedInstance] saveAlbums];
ViewController
.Documents
( iExplorer). , . , . Forrest->Run(speed, distance);
Forrest
;Run
;Array(speed, distance).
NSInvocation
, ( ), . , , . «», ( => ). , .UIToolbar
NSMutableArray
(undo stack).ViewController
, : UIToolbar * toolbar; NSMutableArray * undoStack; // , push pop
viewDidLoad:
" // 2
": toolbar = [[UIToolbar alloc] init]; UIBarButtonItem * undoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoAction)]; undoItem.enabled = NO; UIBarButtonItem * space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; UIBarButtonItem * delete = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteAlbum)]; [toolbar setItems:@[undoItem, space, delete]]; [self.view addSubview:toolbar]; undoStack = [[NSMutableArray alloc] init];
viewDidLoad
. , ViewController
«». ViewController.m : - (void)viewWillLayoutSubviews { toolbar.frame = CGRectMake(0, self.view.frame.size.height - 44, self.view.frame.size.width, 44); dataTable.frame = CGRectMake(0, 130, self.view.frame.size.width, self.view.frame.size.height - 200); }
- (void)addAlbum:(Album *)album atIndex:(int)index { [[LibraryAPI sharedInstance] addAlbum:album atIndex:index]; currentAlbumIndex = index; [self reloadScroller]; }
- (void)deleteAlbum { // 1 Album * deletedAlbum = allAlbums[currentAlbumIndex]; // 2 NSMethodSignature * sig = [self methodSignatureForSelector:@selector(addAlbum:atIndex:)]; NSInvocation * undoDeleteAction = [NSInvocation invocationWithMethodSignature:sig]; [undoDeleteAction setTarget:self]; [undoDeleteAction setSelector:@selector(addAlbum:atIndex:)]; [undoDeleteAction setArgument:&deletedAlbum atIndex:2]; [undoDeleteAction setArgument:¤tAlbumIndex atIndex:3]; [undoDeleteAction retainArguments]; // 3 [undoStack addObject:undoDeleteAction]; // 4 [[LibraryAPI sharedInstance] deleteAlbumAtIndex:currentAlbumIndex]; [self reloadScroller]; // 5 [toolbar.items[0] setEnabled:YES]; }
NSMethodSignature
). NSInvocation
, — undoDeleteAction
.NSInvocation
:undoDeleteAction
, add it to the imaginary "stack" ...push
and pop
. Everything. - Approx. per.LibraryAPI
and update the scroller.NSInvocation
, you need to keep in mind the following:2
. Indexes 0
and are 1
reserved for the target and selector.retainArguments
. - (void)undoAction { if (undoStack.count > 0) { NSInvocation * undoAction = [undoStack lastObject]; [undoStack removeLastObject]; [undoAction invoke]; } if (undoStack.count == 0) { [toolbar.items[0] setEnabled:NO]; } }
lastObject
+ removeLastObject
. , ViewController
. — .NSInvocation
invoke
. , , ( ) — .NSMutableArray
— » . , (« , , »).showDataForAlbumAtIndex:
if (albumIndex < allAlbums.count)
, (albumIndex >= 0)
? , NSArray
, AppCode :int
NSUInteger
, — — «by design».@
x128 .
ru , - .Source: https://habr.com/ru/post/202960/
All Articles