Showing posts with label iOS. Show all posts
Showing posts with label iOS. Show all posts

Thursday, July 21, 2022

How to get your app rejection on App Store (and how to fix it)



App Content Is Inappropriate

  • Apps related to sensitive content, such as porn and gambling, are automatically rejected. They will also not approve content about terrorism, racism, violence, substance abuse. Generally, if your app offends people or encourages them to break the law, you will probably get a rejection approval
  • In this case, either you modify the content or not launch it into the Apple Store.


You Have No Privacy Policy

  • Apple requires the application to take the user privacy data seriously to comply the GDPR in Europe and various FTC rules in the US.
  • To avoid rejection, make sure that users have been aware of the Term Of Use, Privacy Policy.
  • It should explain how you use the specific data that you collect: how it’s stored, and whether or not you share it with other third-party entities.
  • Lastly, the user has full permission for his own data, even asking to remove it: making a function for request to remove data is a reasonable choice. 


Missing Sign in with Apple for third party sign up

  • Apps that exclusively use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option


App Wants to Share Personal User Data

  • In general, users must have control over their data at all times. Users must notice how and why the application would like to use their data and privacy: photos, location, notification...
  • The key is being declaration right and enough permission will avoid you to be rejected in this section.


The App Is a Copy of Another App

  • Apps that are cloned from another app on the Apple Store will get rejection. You should come up with a unique app concept.
  • Another case is spamming, the same app but multiple variants. For example: same app feature and UI but different theme, data...


Hardware and Software Are Not Compatible

  • App can not only run on iPhone but also on iPad as well and vice versa


You’re Using Private API

  • Private APIs are the API used internally by Apple. They are undocumented or  don't officially documented by Apple. The APIs are not stable and can be changed anytime, so they're less guarantee
  • Make sure your code base and even the external libraries don't mess with these or wait for a brutal rejection.


Bugs and Crashes Occur During the Review Process

  • If any crash or significant bug or performance issue (ex: http 500, no network, lagging...) appears during the process, the app will be rejected immediately. Those who perform the review process are human and they test on the physical device (not on an  emulator).
  •  To avoid this, please take the QA step seriously.


Unusually Long Load Times

  • Apps that take long loading times may cause poor user experience, therefore, could be rejected. To prevent this kind of issue, simplify the user interface and optimize your code to make it run faster, compressing images and assets to reduce load times.
  • For apps that need to connect to a server, you only fetch required data and utilize the caching mechanism.
  • For cold startup, it's a good idea to add a splash screen


Placeholder Content Is Still in the App

  • A placeholder or dummy content is a sign of an uncompleted app. A not yet complete app violates the App Store rules, so the app will be rejected.
  • Inspecting every corner of your app ensures that you catch these simple mistakes before submission.


Broken Links in the App

  • Broken links in apps will also be rejected by Apple. For reviewers, it's a sign of poor performance application may affect to user. Thoroughly check all your app links before submit the app.


App Appears to Be Unfinished

  • Even if the app is complete, the mistake in app name (app beta, app dev, app demo...) or build version (0.x) may get your app violate the incomplete app case. The app to submit to stores are considered ready for distribution.
  • Be aware.


Incomplete or Inaccurate Metadata

  • This is about the meta data you put on the App Store Dashboard: app descriptions, screenshots, age ratings, payment options, and privacy information. The key is honest, sincere and transparent. Do not cheat.
  • Remember to upload screenshots for every screen size on the iPhone/ iPad. Do not use any other exotic template device (ex: Android) but an iPhone/iPad.
  • Do not mention any keywords about different platforms (ex: Android)


Low-Quality UI

  • Apple has provided a detailed description of how an app should be designed to comply with their platform (Human Interface Guidelines). 
  • A poor UI design that fails to meet standards may get rejected. 
  • Please make sure your app looks fantastic and consistent across all Apple devices.

Saturday, December 4, 2021

iOS: QoS - Quality of service

The Quality of Service (QoS) class classifies what you want dispatchQueue to do. Indicates the importance of your app by specifying the quality of the task. When scheduling tasks, the system prioritizes tasks with higher service classes. Higher priority tasks are faster than lower priority tasks and are performed using more resources, which typically requires more energy than lower priority tasks. You can ensure your app's responsiveness and energy efficiency by specifying exactly the right QoS class for what your app does.

priority

userInteractive > userInitiated > default > utility > background


userInteractive

actions that interact with the user, such as working on the main thread, refreshing the user interface, or performing animations. focus on responsiveness and performance.

The task is almost instantaneous.


userInitiated

this class assigns it to tasks that provide immediate results for what the user is doing or prevent the user from using the app. for example, you can load the contents of the email that you want to display to the mercenary. you need immediate results, such as opening documents saved as user-initiated tasks,or performing tasks when a user clicks something in the user interface. work is required to continue user interaction. focus on responsiveness and performance.

Tasks like seconds or less are almost instantaneous.


default

The default quality of service class. Assign this class to a task or queue that your app starts or uses to perform active tasks on behalf of the user. This QoS is not used by developers to classify tasks. QoS is used as the default for unspecified operations and runs at the GCD global queue level.


utility

a quality of service class for tasks that are not actively tracked by the user. tasks that take time to complete and do not require immediate results, such as downloading or importing data. utility operations typically have progress bars that are visible to the user. it focuses on providing a balance between responsiveness, performance and energy efficiency.

The operation takes a few seconds to a few minutes.


background

tasks that work in the background and are not visible to the user, such as indexing, synchronization, and backup. focus on energy efficiency.

Tasks that take a considerable amount of time and require minutes or hours


unspecified

There are no quality of service classes. This indicates that there is no QoS information and signals that QoS should be inferred into the system. If a thread uses a legacy API, the thread can have unspecified QoS.

Reference:

iOS: GCD - Grand Central Dispatch



GCD is an API that performs parallelism programming or multithreading operations. In other words, the GCD API handles which tasks to do, one sequentially, multiple simultaneously, sync or Async. You can choose the right queue and QoS for your situation to improve execution efficiency.

Dispatch Queue


GCD provides Thread Safe one Dispatch Queue to perform Multi-threading operations. All Dispatch Queues are FIFO data structures. Therefore, the task always starts in the order in which it was added.

Serial(main)


Serial queues are serial queues that process them one by one in the order in which they were added to the queue. One Task must be completed to process the next Task.



Because all events related to the UI are attached to Main Thread, all UI operations must be performed on the Main Queue.

Concurrent(global)


Component queues are parallel queues that execute multiple operations at the same time. Task runs in the order in which they were added to the queue. The number of tabs that can be run at once is variable and depends on system conditions. Global Queue provides quality of service (QOS)for prioritizing tasks.


When sending tasks to global concurrent queues, they do not specify a direct priority. Instead, specify a Quality of Service (QOS) class property: this indicates the importance of the task and helps the GCD determine the priorities to assign to the task.

Sync, Async


Sync (synchronous)

After the thread registered with the queue is finished, run the next queue sequentially.


Async (asynchronous)

The threads registered in the queue do not wait until the end of the operation, and the next queue is run sequentially and simultaneously. The end of a task is not sequential and can vary from task to task. The difference is whether to wait until one task is completed and then run the next. In the case of Sync, you should use Async because you can't do anything if you use it if it takes a long time or you're uncertain when it's going to be done. Depending on which queue uses synchronous asynchronous, you can use it as follows:
  • Serial
    • Sync
    • Async
  • Concurrent
    • Sync
    • Async


Using Dispatch Queue


Create DispatchQueue

// Serial Queue
DispatchQueue.main.sync { } // CRASH!
DispatchQueue.main.async { }
DispatchQueue(label: "com.CustomSerialQueue").sync { }
DispatchQueue(label: "com.CustomSerialQueue").async { }

// Concurrent Queue
DispatchQueue.global().sync { }
DispatchQueue.global().async { }
DispatchQueue(label: "com.CustomConcurrentQueue", attributes: .concurrent).sync { }
DispatchQueue(label: "com.CustomConcurrentQueue", attributes: .concurrent).async { }
  






DispatchQueue.main.sync If you use , you will get an error. The reason for this is that if you sync to the main queue while working on the main queue and block it, you will fall into a deadlock. If you synchronise serial queues, you can create and use custom queues rather than main.


Serial queue - Sync

Because other tasks are blocked until the end of the queue's work, one operation must be completed before the other runs sequentially.

let serialQueue = DispatchQueue(label: "serialQueue")

serialQueue.sync {
  for i in 0...3{
    print("\(i) [serial_sync_1]")
  }
  print("--------------------------")
}

serialQueue.sync {
  for i in 0...5{
      print("\(i) [serial_sync_1]")
  }
  print("--------------------------")
}

for i in 0...5{
  print(i)
}


======== Result ========

0 [serial_sync_1]
1 [serial_sync_1]
2 [serial_sync_1]
3 [serial_sync_1]
--------------------------
0 [serial_sync_2]
1 [serial_sync_2]
2 [serial_sync_2]
3 [serial_sync_2]
--------------------------
0
1
2
3  


Serial queue - Async

Because it is a serial queue, the operations entered the queue are processed in order, although other operations are still processed by running the queue asynchronously.

let serialQueue = DispatchQueue(label: "serialQueue")

serialQueue.async {
  for i in 0...3{
    print("\(i) [serial_async_1]")
  }
  print("--------------------------")
}


serialQueue.async {
  for i in 0...3{
    print("\(i) [serial_async_2]")
  }
  print("--------------------------")
}

for i in 0...5 {
  print(i)
}


======== Result ========

0
0 [serial_async_1]
1
1 [serial_async_1]
2
2 [serial_async_1]
3
3 [serial_async_1]
4
--------------------------
0 [serial_async_2]
5
1 [serial_async_2]
2 [serial_async_2]
3 [serial_async_2]
--------------------------


Concurrent queue - Sync

In the serial queue, one operation must be completed, such as sync, before the other runs sequentially.

DispatchQueue.global().sync {
  for i in 0...3{
    print("\(i) [global_sync_1]")
  }
  print("--------------------------")
}

DispatchQueue.global().sync {
  for i in 0...3{
    print("\(i) [global_sync_2]")
  }
  print("--------------------------")
}

for i in 0...3 {
  print(i)
}


======== Result ========

0 [global_sync_1]
1 [global_sync_1]
2 [global_sync_1]
3 [global_sync_1]
--------------------------
0 [global_sync_2]
1 [global_sync_2]
2 [global_sync_2]
3 [global_sync_2]
--------------------------
0
1
2
3

Concurrent queue - Async

Because we process tasks simultaneously in parallel, we don't know what will start and end first.

DispatchQueue.global().async {
  for i in 0...3{
    print("\(i) [global_async_1]")
  }
  print("--------------------------")
}

DispatchQueue.global().async {
  for i in 0...3 {
    print("\(i) [global_async_2]")
  }
  print("--------------------------")
}

for i in 0...3 {
  print(i)
}

======== Result ========

0
0 [global_async_2]
0 [global_async_1]
1
1 [global_async_2]
1 [global_async_1]
2
2 [global_async_1]
3
2 [global_async_2]
3 [global_async_1]
3 [global_async_2]
--------------------------
--------------------------
Reference:

Wednesday, April 11, 2018

iOS : Choose the crypto flow

Even though there are multiple tools for doing just that, not all of those tools are equal. By just taking some random algorithm from CommonCrypto and using StackOverflow example to implement it, you'll fail. Remember, cryptography is hard (read this essay and this presentation), and it's very easy to get it wrong.

So, you need to make your choice consciously. While thinking about your cryptographic tools, it's very useful to keep your goal in mind:

protect the sensitive data in certain context from modification and 3rd party eyes, and be able to trust sender of the data.

Tuesday, April 5, 2016

When to use map, flatMap, or for loops in Swift

Use map when you need to transform arrays

let arrayOfNumbers = [1, 2, 3, 4]
let arrayOfString = arrayOfNumbers.map { "\($0)" }

In the context of Array map get an array, applies a transformation function to every element, and returns a new array with the resulting elements. . That's the best use case for map.

Use for loops when there are "side effects"

Without going into details an operation has a side effect if it results in some kind of state changing somewhere, for example changing the value of a variable, writing to disk, or updating the UI. In such case using a for loop is more appropriate.

for number in arrayOfNumbers {
  print(number)
}

And what about flatMap?

When you need to transform the contents of an array of arrays, into a linear array use flatMap:

let users: [User] = ...
let allEmails = users.flatMap { $0.emails }

p/s: No difference about performance.

Thanks to this

Saturday, April 2, 2016

Build Apps Faster Than Ever Before

Setup an Xcode Derived Data RAM Disk

Would you prefer the time it takes to compile and link your app to be faster or slower?

If you are not using a RAM disk, then you may be choosing the latter answer. If you don’t already know how to set this up, I’ve created an easy to use script written in Swift 2.2. This one may best suit the pros but we can all appreciate a little less waiting for a computer.

As a side note, Swift may not currently be the most efficient way to write scripts, but being able to do it has a special kind of sparkle that lights up when it is done.

Using the script allows the Derived Data setting to stay the same in Xcode but runs out of RAM instead of the disk. It does this by mounting the RAM disk to the same path that is normally disk-based.

This saves wear-and-tear on solid state drives, if you are into that kind of thing. If you happen to be using a spinning disk, it’s way faster than that, too.

I’ve been running Xcode like this for years and over that time all those savings can really add up.
It’s easy to create a launch agent, simply a property list file, that allows having the script run every time your computer starts. I’ve included an example of that in the source code.
I’ve even made sure using this technique works with Instruments because there is a need for Spotlight to find symbol files during profiling and debugging.

The script is contained in an Xcode project. The file main.swift can be copied out and renamed to setupXcodeDerivedDataRAMDisk.swift and placed in a bin directory for running from the command-line or a launch agent.
Here is the link to the project.

Original post

Sunday, May 24, 2015

[iOS] Where should I store my data?


  • Critical data that cannot be recreated, such as documents or user-specific data that would be lost if the device were damaged, goes into the <Application_Home>/Documents directory and will be backed up by iCloud unless otherwise specified.

  • Cached data that can be recreated, such as a local database or downloaded images, goes into the <Application_Home>/Library/Caches directory and will not be backed up by iCloud. This data may get purged at some point if iOS runs low on disk space.

  • Temporary data that is transient and not used between app launches, such as a temporary file cache, goes into the <Application_Home>/tmp directory and will not be backed up by iCloud. You should always remember to delete files stored here yourself.

  • Offline data that needs to be persistent and available when the device is offline (such as Airplane Mode), goes into the <Application_Home>/Library/Private Documents directory and will not be backed up by iCloud, but also will not be purged by iOS in a low disk space situation. For more information about Private Documents in iOS, see QA1699.

Saturday, May 16, 2015

[iOS] Difference between Show, Show Detail, Present Modally, Popover Presentation

1. Show - Pushes the destination view controller onto the navigation stack, moving the source view controller out of the way (destination slides overtop from right to left), providing a back button to navigate back to the source - on all devices.
Example: Navigating inboxes/folders in Mail.
2. Show Detail - Replaces the detail/secondary view controller when in a UISplitViewController with no ability to navigate back to the previous view controller.
Example: In Mail on iPad in landscape, tapping an email in the sidebar replaces the view controller on the right to show the new email.
3. Present Modally - Presents a view controller in various different ways as defined by the Presentation option, covering up the previous view controller - most commonly used to present a view controller that animates up from the bottom and covers the entire screen on iPhone, but on iPad it's common to present it in a centered box format overtop that darkens the underlying view controller.
Example: Tapping the + button in Calendar on iPhone.
4. Popover Presentation - When run on iPad, the destination appears in a small popover, and tapping anywhere outside of this popover will dismiss it. On iPhone, popovers are supported as well but by default if it performs a Popover Presentation segue, it will present the destination view controller modally over the full screen.
Example: Tapping the + button in Calendar on iPad (or iPhone, realizing it is converted to a full screen presentation as opposed to an actual popover).
5. Custom - You may implement your own custom segue and have complete control over its appearance and transition.

Saturday, May 9, 2015

[iOS] Sai dấu khi encode url hoặc gửi chuỗi lên server

Đôi khi gửi một chuỗi đc encode lên server bằng phương thức post, một số dấu ko đc hiển thị đúng

send: 2013-06-29T18:33:17+0000
receive: 2013-06-29T18:33:17 0000. //+ ---> " "

hoặc encode một chuỗi url ra kết quả không đúng. (&/+/khoảng trắng...)

Ta khắc phục điều này bằng hàm sau CFURLCreateStringByAddingPercentEscapes

#import "NSString+URLEncoding.h"
@implementation NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding {
    return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
               (CFStringRef)self,
               NULL,
               (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
               CFStringConvertNSStringEncodingToEncoding(encoding));
}
@end
Reference:
http://kiririmode.hatenablog.jp/entry/20110730/p1

http://stackoverflow.com/questions/17444876/ios-not-encoding-plus-sign-in-x-www-form-urlencoded-post-request

Sunday, May 3, 2015

[iOS] Tạo class DataSource cho TableView

Mở đầu

Để tiếp nối chuỗi bài về TableView, hôm nay mình cũng viết một bài liên quan đến TableView. Trong iOS TableView là class được dùng khá nhiều.
Khi dùng TableView chúng ta thường phải set datasource và delegate cho TableView. Thường thì datasource của TableView là một array.
Khá nhiều bạn thường set datasource cho Tableview ngay trong ViewController (tableview.datasource = self). Và khi đấy trong ViewController chúng ta luôn luôn phải implement delegate cho TableViewDataSource như sau:
// TmpViewController.m

#pragma mark - UITableViewDataSource delegate
- (NSInteger)tableView:(UITalbeView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return [self.dataArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *cellIdentifier = @"MyCell";
  // lấy cell có sẵn
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  // nếu không có cell có sẵn thì tạo cell mới
  if(cell == nil) {
    cell = [UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                 reuseIdentifier:cellIdentifier];
  }

  // lấy dữ liệu cho cell hiện tại. (Ví dụ dữ liệu là NSString)
  NSString *item = [self.dataArray objectAtIndex:indexPath.row];
  // gán dữ liệu cho cell
  [cell.textLabel setText:item];

  return cell;
}
Việc viết như trên đối với những ứng dụng nhỏ thì không vấn đề gì nhưng khi ứng dụng sử dụng nhiều tableview thì trong từng ViewController chúng ta luôn phải viết đi viết lại đoạn code trên. Nếu nhìn kỹ đoạn code trên bạn sẽ thấy thực ra với mỗi TableView khác nhau chúng ta chỉ cần thay đổi phần #gán dữ liệu cho cell tuỳ theo cấu trúc của từng cell. Còn đâu những phần còn lại chúng ta có thể sử dụng lại code. Ngoài ra nếu chúng ta để những đoạn code này trong ViewController sẽ khiến ViewController trở nên dài hơn bởi vì bản thân ViewController đã chứa rất nhiều code như delegate, code xử lý sự kiện, gesture. Vì vậy để có một ViewController ngắn gọn hơn, dễ hiểu hơn, lại tăng tính sử dụng lại code chúng ta sẽ tạo 1 class datasource riêng tên là TVArrayDataSource.


Tạo class TVArrayDataSource

Vậy chúng ta sẽ chuyển hết code ở trên sang class TVArrayDataSource và trong các ViewController chúng ta chỉ cần viết phần #gán dữ liệu cho cell tuỳ theo cấu trúc của cell. Vậy trong TVArrayDataSource cần những property gì?
Đầu tiên là NSArray *items trỏ đến array data của chúng ta trong ViewController để chúng ta có thể lấy data tương ứng cho từng cell và cell identifier NSString *cellIdentifier là string dùng để định danh cell.
// TVArrayDatasource.m


@interface TVArrayDataSource()

@property (strong, nonatomic) NSArray *items;
@property (copy, nonatomic) NSString *cellIdentifier;

@end

@implementation TVArrayDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return [self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // tìm cell có sẵn
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier];
    // tạo cell mới nếu không tìm thấy
    if (cell == nil) {
      ...
    }

    // lấy data cho cell
    id item = [self.items objectAtIndex:indexPath.row];

    // gán dữ liệu cho cell
    ...

    return cell;
}

@end
Đầu tiên chúng ta sẽ nói về đoạn ... tại phần gán dữ liệu cho cell. Tại vì tuỳ từng trường hợp của tableview mà cell của chúng ta có cấu trúc khác nhau, data source có cấu trúc khác nhau nên phần gán dữ liệu này là khác nhau. Do đó tại đây chúng ta có thể gọi đến các hàm callback trong ViewController để gán dữ liệu cho cell theo cách mà chúng ta muốn. Có nhiều cách như dùng block, selector hay delegate. Mình thì thấy tiện nhất và ngắn nhất là block và selector nên mình sẽ tạo class TVArrayDataSource có thể dùng block hoặc selector.
Với block thì chúng ta cần tạo 1 property để lưu block và execute block tại đoạn gán dữ liệu. Chúng ta sẽ thêm block property vào TVArrayDataSource.m và tạo 1 method khởi tạo dataSource với block như sau:
/// TVArrayDataSource.m

typedef void (^TVCellConfigureBlock)(id, id);

@interface TVArrayDataSource : NSObject <UITableViewDataSource>

/* khởi tạo datasource với block */
- (id)initWithItems:(NSArray *)items
     cellIdentifier:(NSString *)cellIdentifier
 cellConfigureBlock:(TVCellConfigureBlock) configureBlock;

// TVArrayDataSource.m

...
// thêm block property vào
@property (copy, nonatomic) TVCellConfigureBlock configureBlock;

// và method khởi tạo chỉ đơn giản như sau
- (id)initWithItems:(NSArray *)items
     cellIdentifier:(NSString *)cellIdentifier
 cellConfigureBlock:(TVCellConfigureBlock)configureBlock
{
    self = [super init];
    if(self) {
        self.items = items;
        self.cellIdentifier = cellIdentifier;
        self.configureBlock = configureBlock;
    }
    return self;
}

// và chúng ta thêm phần execute block tại đoạn gán dữ liệu cho cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   // tìm cell có sẵn
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier];
    // tạo cell mới nếu không tìm thấy
    if (cell == nil) {
      ...
    }

    // lấy data cho cell
    id item = [self.items objectAtIndex:indexPath.row];

    // execute block để gán dữ liệu cho cell
    self.configureBlock(cell, item);

    return cell;
}

Khi đó bên ViewController chúng ta chỉ cần tạo 1 block để thực hiện việc gán dữ liệu cho cell. Và block này sẽ được execute bằng self.configureBlock(cell, item) với tham số là cell hiện tại và data tương ứng của cell. Bởi vì tham số của block là cell hiện tại và data cho cell đấy nênchúng ta hoàn toàn có thể tự do tuỳ chỉnh cell theo ý muốn. Và code bên ViewController sẽ rất ngắn và đẹp như sau:
/// ViewController1.m

// configure block. Kiểu tham số có thể tuỳ chỉnh theo kiểu data bất kỳ của bạn.
TVCellConfigureBlock configureCell = ^(CellClassName *cell, DataType *name) {
  // gán dữ liệu cho cell. ví dụ như sau:
  [cell.title setText:name];
};
// tạo instance dataSource của TVArrayDataSource và khởi tạo với block ở trên
dataSource = [[TVArrayDataSource alloc] initWithItems:items
                                       cellIdentifier:@"MYCELL"
                                   cellConfigureBlock:configureCell];
tableView.datasource = dataSource;
Bạn thấy đấy giờ trong ViewController thì phần code cho dataSource của tableView khá là đẹp.
Đôi khi bạn muốn viết đoạn gán dữ liệu cho cell vào một method khác trong class ViewController thay vì dùng block. Để cho những trường hợp đó như đã nói ở trên chúng ta có thể dùng selector. Tương tự như block chúng ta cũng sẽ tạo một @property (assign, nonatomic) SEL configureSelector; và đối tượng để execute method của selector này @property (weak, nonatomic) id target; (Đối tượng này chính là ViewController). Chúng ta cũng cần tạo một hàm khởi tạo datasource khác với selector. Cuối cùng trong phần gán dữ liệu cho cell chúng ta execute method của selector với objc_msgSend(self.target, self.configureSelector, cell, item);. Do phần này tương tự như đối với block nên mình không giải thích thêm mà các bạn có thể xem code trên github.
Tiếp theo còn một đoạn ... tại phần tạo cell mới khi mà không tìm thấy cell có thể dùng lại. Như bạn thấy đấy để tạo cell mới chúng ta cần biết Class của cell. Với Objective-C chúng ta có thể tạo 1 instance từ tên class. Khi đó chúng ta có thể tạo 1 cell như sau:
cell = [[NSClassFromString(CELL_CLASS_NAME) alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.cellIdentifier];
Như vậy class TVArrayDataSource chỉ cần có thêm thông tin là tên class của cell là mọi việc có thể hoàn tất. Ngoài ra nhiều khi chúng ta muốn tạo cell từ file Xib. Để tạo cell từ file xib chúng ta cũng chỉ cần biết thêm tên file xib. Thế nên mình tạo thêm một property cellName để lưu tên class của cell hoặc tên file Xib tuỳ theo trường hợp cell tạo từ file xib hay từ code.
Như vậy việc tạo class TVArrayDatasource đã hoàn thành. Và bây giờ trong ViewController chúng ta chỉ implement đoạn code ngắn như sau:
Khi sử dụng với block
// ViewController.m

// tạo block
TVCellConfigureBlock configureCell = ^(CELL_CLASS_NAME *cell, DATATYPE *name) {
  [cell.title setText:name];
};

dataSource = [[TVArrayDataSource alloc] initWithItems:items
                                       cellIdentifier:@"MYCELL"
                                   cellConfigureBlock:configureCell];
[dataSource setXibFileName:@"XibFileName"];
tableview.datasource = dataSource;
Hoặc khi sử dụng với selector.
// ViewController.m
dataSource = [[TVArrayDataSource alloc] initWithItems:items
                                       cellIdentifier:@"MYCELL"
                                               target:self
                                     cellConfigureSel:@selector(configureCell:andItem:)];
[dataSource setCellClassName:@"CELL_CLASS_NAME"];
tableView.dataSource = dataSource;


// selector
- (void)configureCell:(CELL_CLASS_NAME *)cell andItem:(DATA_TYPE *)item
{
    [cell.title setText:item];
}


Tổng kết

Bài viết trình bày về cách tạo class datasource riêng cho tableView thay vì implement trực tiếp trong ViewController. Điều này sẽ giúp ViewController ngắn gọn hơn và code trông đẹp hơn, cũng như tăng khả năng sử dụng lại code. Chúng ta có thể dùng lại class TVArrayDataSource tại nhiều ViewController mà không cần phải implement lại các hàm delegate của TableViewDataSource. Thế nhưng hiện tại class này chỉ dùng cho những tableview có 1 section.
Toàn bộ code của class này cũng như sample bạn có thể tham khảo tại: https://github.com/ktmt/TVDataSource
Hoặc để sử dụng class này bạn có thể cài qua coccoapod bằng cách thêm pod 'TVArrayDataSource' vào Podfile.
Bài viết lấy từ : nghialv.com

Work with vanilla UICollectionViewCell

Problem

Sometimes, you'll work with a very big UICollectionView, ex: A cell contains a UICollectionView, a cell contains a UITableView, a cell contains a bundle UIView and a lot of Auto Layout constraint.
Especially, in that cell contains UIImageView, when you scroll on the screen, it's laggy.

How to solve that

Determine issues

Why does it happen?
  • Rendering UIImage on UIImageView is take time.
  • Draw the Image when scrolling is take time.
  • Auto Layout is take time.
  • Draw UIImageView is take time.
  • Caching on disk is take time.
How I solve that?
  • For rendering UIImage on UIImageView, firstly, avoid use native setUIImage method or setImageWithURL of AFNetworking now. Why?. Look at SDWebImage and see what happen :)
  • For drawing the Image when scrolling, use below method when setup cell.
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
  • For Auto Layout, it is a problem on iOS 8, make the cell have auto layout scroll very laggy. So this is solution: Override 1 method in UICollectionViewCell class.
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    return layoutAttributes;
}
  • For the problem of UIImageView, some properties of it will decrease the performance, ex: clipToBounds
  • Writting and Reading on Disk is taked more time than Memory, therefore, take care when working with Caching.
Sourcec: http://kipalog.com/posts/ARyQ1xPVy2CG6coH711lFg

Tuesday, October 14, 2014

[iOS] Lưu dữ liệu vào NSUserDefaults chống user có thể sửa dữ liệu.

Như ta đã biết, dùng NSUserDefaults để lưu trữ các param, setting… trong khi code rất tiện, chỉ gọi các lệnh set, get là lưu được tất cả các loại format data.
Tuy nhiên NSUserDefaults lưu data ra file, không có bảo mật, nên user có thể dễ dàng sửa file, làm game chạy sai.
Dùng class này thì sẽ chống được việc sửa data trực tiếp từ file. Nó hoạt động theo cơ chế tạo thêm data hash cho data cần lưu, rồi lưu luôn vào cùng với data trong NSUserDefaults. 
Hạn chế của cách trên là nó không che giấu được data, nó chỉ chống không cho sửa. Do vậy chỉ có thể dùng để lưu param, setting… chứ không lưu username, pass được.

https://github.com/matthiasplappert/Secure-NSUserDefaults
 
 // Configuring user defaults. It is recommended that you do this  
 // immediately after the app did launch.  
 [NSUserDefaults setSecret:@"shh, this is secret!"];  
 // Write secure user defaults  
 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];  
 [defaults setSecureBool:YES forKey:@"IsRegistered"];  
 // Read secure user defaults  
 BOOL valid = NO;  
 BOOL registered = [defaults secureBoolForKey:@"IsRegistered" valid:&valid];  
 if (!valid) {  
   // the property has been modified, handle this situation  
 } else {  
 // Valid property, do whatever you need to do  
 }  

Nguồn