Saturday, December 30, 2017

Unable to connect iPhone's hotspot to Windows 8 PC

I'm using iPhone 6 and iOS 10. My laptop is Macbook Pro 13'' late 2011 but I installed Windows 8 on it. I have successfully connected to the hotspot from my laptop many times already but it just stopped working today. It said the connection is limited, and when I tried to access a web page, i said no internet connection. I then tried to connect to the hotspot from my iPad and it worked. I got connection. I restarted my laptop but it still didn't work. I turned off the Cellular network on my iPhone and then turned it back on, still no luck.

Solution

The solution is renaming my iPhone and restarting the hotspot. I renamed it by go to Settings > General > About > Name.


Wednesday, December 13, 2017

The movie subtitle appeared slower or faster within both Browser and media player programs


There are a bunch of web sites for watching movies free online with subtitles. I often use a browser add-on like Flash Video Downloader to download the movies for watching offline due to slow internet connection, which makes the video keep buffering and pausing like every 5 seconds. Note that the subtitles is not embedded in the video files so I have to download those subtitles from different sites so that I can add them in media player program like VLC. However, the subtitles i downloaded from english-subtitles.org and yifysubtitles.com didn't work correctly. The subtitles either appeared slower or faster than the videos. It's because almost all of the subtitles on the sites were made for different sizes of videos. For example, the videos on those free online sites were 720px HD but the subtitles were made for 480px HD or 1048px HD.

The only one that works for me so far is opensubtitles.org. This site has different subtitles uploaded by different author for different sizes of a video (480px HD, 720px HD, 1048px HD, and so on). Usually, the subtitles for 720px HD made by the author named Blueray is the default one. In other words, it will be downloaded when the user clicks on the Download button. And that one normally works. However, you can download other subtitles uploaded by other authors too and try them out.

Fake the dropbox referrals to get more spaces

If it's hard for you to convince your friends to sign up and install dropbox so that you can gain more spaces as they're your referrals, you can create different mail accounts yourself and send invites to those emails. But this would not work if you're using the same computer to install dropbox and sign in with more than one account. Dropbox identifies your machine based on Mac address or Network Card's address. The addresses are universally unique.

The solution is using virtual machine, in which you can set your own Mac address for the Guest OS. In my case, I was using VirtualBox and followed the steps below.
1. (optional) If you're already in the Guest OS and the dropbox was already installed and linked, go to Settings of Dropbox then Accounts tab and click on "Unlink this computer" button. Shutdown the virtual machine.
2. Select on the virtual machine and go to Settings then change the Mac address under Network section.
3. Start the virtual machine
4. Sign into the invited email account and click on the Invite button in the email message
5. You'd get redirected to the sign up page of Dropbox. Follow the instructions to sign up and then the Dropbox program would automatically get downloaded at the end.
6. Install the Dropbox program, start it, and sign in with the invited email. You're done!

Problem 1

I was going to create 10 Gmail accounts as my referrals but a phone number could be used to verify only three accounts when signing up and I had only one phone.

The simple solution is using different mail services such as outllook.com.

Problem 2

In step 5 I noticed that I got the error message "Please enter the CAPTCHA resopnse" when I clicked on the submit button when it's the third time I've signed up on dropbox. I think it's a Dropbox's bug. It tried to prevent robotic spammers but it didn't provide a place to enter the captcha.

I found a workaround to bypass the captcha. I used VPN client to change my IP address so Dropbox couldn't track how many times I signed up anymore.



Sunday, December 10, 2017

What is Agile Software Development?

I was always confused when the words Agile, Software Development Life Cycle (SDLC), and Software Engineering came into my mind until I read the book "The Complete Software Developer's Career Guide". The book does not dive deep into details. It just gives the overall ideas of how those words mean and it's what i've been looking for.

1. Introduction

Software engineering is about how you design and develop your software effectively. In traditional approaches, there are 6 main phases in developing software such as Analysis, Design, Implementation, Testing, Deployment, and Maintenance. These phases define the life cycle of software development.

Software development methodology, as its sounds, is a method/way/principle/workflow to develop software. There were a bunch of traditional methodologies of software development and the most well-known one was Waterfall.

The Watterfall approach requires one phase of SDLC must be completed before you could go to next one. For instance in Analysis phase, you need to gather all the requirements of the whole system and finalize them before you could go on to the Design phase (design UI and system using UML). The bad thing is for example if the requirement need to be changed for some reasons while you're already halfway in the Implementation phase, you can either go back to each phase and do it again or end up delivering the system the customer doesn't want. Just a small change in the requirement could make a big different in design and implementation phases as most parts of the system has already be done. This is why Agile was invented.

Agile software development involves developing software in iterations and adapting to requirement change. All the requirements do not need to be finalized ahead because only one or two of them are chosen to be implemented per iteration. Each iteration must go through the Analysis, Design, Implementation, Testing, and Deployment phases and may last 1 or 2 weeks. As the developers are required to focus on the prioritized features, the impact of requirement change on the work they've done is less or none. Some people are stick to one Agile methodology while some others combine one or two methodologies together even with their own methods.

2. Scrum 

It is one of the Agile methodologies that defines the roles of software development teams, the workflow for developing software, and what meeting should be set up in each iteration of development, also known as a sprint. Ken Schwaber and Jeff Sutherland started to write about Scrum at the same time in the early 1990. Then in 1995, they wrote a joint paper merging their own approaches together to define Scrum methodology.

Roles

There are two main roles: product owner and scrum master. Product owner communicates between customer and stakeholders/teams and knows everything about the business and prioritize the tasks. I think of this role as project manager. Scrum master is just like a team lead/manager responsible for teaching the team members and solving their problems.

Workflow

All features are called product backlogs. Each product backlog may have several items, in which some of all of them are pulled to work on in a sprint. At the beginning of each sprint, everyone is gathered in a meeting to set estimation for the tasks to be done. There is a stand-up meeting everyday to follow up the progress. Everyone must report what they did yesterday, what they going to do today, and the problem they're having.

Issues

It might put much pressure on each team as they have to report the progress everyday. Everyone must have commitment that they have to complete the task on time even if they have to stay late but it's hard in real life.

3. Extreme Programming (XP)

XP was created by Kent Beck around 1996. Like other Agile methodologies, XP requires developing the system in iterations and adapting to requirement changes. According to the book "Extreme Programming Explained", any ways you do to make the development go smoothly, make the system good, and satisfy the customer to an extreme level are XP. Good communication, helping each other, and being proactive can be called XP. Taking the "best practices" such as unit testing, test driven development, and object-oriented programming is also Extreme Programming. Today, there are more "best practices" such as pair programming, which two programmers sit together to solve one problem. Pair programming help them focus on current important task, and find the best solution and complete it fast.


Friday, December 1, 2017

My favorite VPN clients

If we're connected to VPN server, all requests to any web sites would go to the VPN server (through ISP of course). ISP can't know what web site we're requesting as the message is encrypted. It can only know we're sending request to VPN server. Then, VPN server requests the targeted web site and returns its content back to our PC.

Normally, I use VPN to hide my location. There are a bunch of FREE VPN servers and clients (the programs to connect to the VPN server). Below is the list of VPN clients I've used.

For Windows

- TunnleBear: the speed is fairly good but the data usage is limited per month.
- VPN Gate: the speed is a bit slow but it provides unlimited data usage.


Thursday, November 30, 2017

Google Play is not connected for Clash Of Clans running on KOPLAYER Android emulator

I'm running KOPLAYER Android emulator version 1.4 on Windows 7. I downloaded and installed Clash Of Clans (COC) within the emulator. The game started successfully, but i could not link my existing account. I've player COC for a few years on iPad and iPhone, and I'm now town hall 10. From COC within the emulator, I checked the Settings and saw that "Google Play Sign In" button is Disconnected. I clicked on it and nothing happened. Then, I click on "Link a device" button, and nothing happened either.

I found the solution from this post by following these steps:
- Go to Google Play Store (clicking on the triangle icon)
- Click on the horizontal bars icon at the top left corner
- Select My Apps & Games under Apps & Games section
- Click on the search icon at the top right corner and type "Google Play Games" and click on Update button. It will sign in Google Play automatically after the update has finished.
- Go to Clash Of Clans and link the device.





Wednesday, November 15, 2017

WiFi does not ask for password

I'm now in a coffee shop, which I've never been before. A moment ago, I had problem with connecting with the WiFi here using my MacBook Air Mid 2013 (Mac OS Sierra) but it was ok using my iPhone.

I could see the WiFi name (Amazon BKK 63) in the settings (no padlock symbol though) but it didn't prompt me for password when i clicked on it. It just showed that i'm connected. I then opened Network settings from the System Preferences and saw that "WiFi is connected to Amazon BKK 63 and has the IP address 10.10.10.238", but there was no internet connection.

This is because the WiFi network was not encrypted. Normally, the encrypted networks show the password input dialog box when you choose on the WiFi network name but the unencrypted networks don't.

I tried to access Google from a browser but it just said no internet connection. After that, i opened Terminal and pinged the IP address 10.10.10.238, and i got replies back so the IP was correct and could be reached. I wondered what would happen if i accessed it from browser? I then accidentally typed a wrong IP address, 10.10.10.239, in the browser's address bar and it redirected me to the WiFi's login page. I typed the password and then I got the connection. And when i typed the IP address 10.10.10.238, it said that "The site can't be reached". I have no idea what those IP addresses are.

I've got more tips on how to solve this problem from this web site, and i think i will try one of his methods if i get this problem again in the future. He said we should try to load router's default page by entering these IP addresses: 127.1.1.1, 1.1.1.1, http://localhost, in the browser's address bar. If it still doesn't work, try the IP address shown in the Network setting (10.10.10.238 in my case above) or the router's IP address (e.g. 10.10.10.1) by go to Network settings -> Advanced -> TCP/IP. You can also try to open the company's website.



Wednesday, November 8, 2017

Khmer Unicode Font Error: unable to type two vowels for one consonant

I can't type two vowels for one consonant in a newly created document of both MS Word and OpenOffice for Mac OS Sierra. I couldn't type the word below pronounced as /pei/.
I tried both ways below but it didn't work.
Surprisingly, it worked for the word 'eat' in Khmer.
This problem didn't happen when I typed the word in Chrome's address bar. I tried to type the word using the fonts Khmer OS System, Khmer OS Content, Khmer OS Battambang, Koh Santepheap, and DaunPenh in MS Word and OpenOffice but still no luck. At last, I found one that worked for me. It was Kh Battambang. I think it came with Mac OS by default because it showed in the Computer list in Font Book application.




Microsoft Word document saved in Windows 7 does not display correctly on Mac OS

I created a Microsoft Word document on Windows 7. The fonts I used in the document were Khmer OS Battambang and Khmer Mool. After that I opened it in Microsoft Word on Mac OS Sierra and the text did not display correctly. Please see the image below for how it looked like.


The problem I'm trying to fix in this post is some vowels and consonants overlapped each other. Those texts were displayed using Khmer OS Battambang font. (The square symbols resulted from missing the font Khmer Mool.)

When I copied the text and pasted it to the browser's address bar, they displayed correctly. I then created a new document in MS Word and paste it to the new document, and it still didn't work. It seemed that the problem is with the MS Word for Mac.

There are 2 workarounds:


  1. Installed OpenOffice then opened the document in it, and the text was fine but the symbols in the third line were replaced by square symbols. 
  2. You have to replace Khmer OS Battambang with Kh Battambang and make sure you install the font in another computer too. This is because the Khmer Unicode keyboard layout I installed was created to use with Kh Battambang font. If you need another font similar to Limon R1, you can use Kh Muol.







Friday, October 13, 2017

Testing an iOS app on a device

I'm writing this post based on Xcode 8 (and Swift 3). While developing an iOS app, we can test it on either Simulator or an actual device. To test your app on the actual device, your app must be code signed and provisioned because the OS does not allow the untrusted app to run and use certain services (such as In-App Purchases, Game Center, and Push Notification).

Code signing

It is the way to ensure the authenticity of executable code (app). Using code signing, the operating system can know whether or not the app is altered by other developers (or malicious softwares) while in transit. It uses Public Key Cryptography and Hashing algorithm. The app's developer creates a hash value of the program and encrypts it with a private key. The encrypted hash and a public key are embedded with the app and sent to the users. When the users launch the app, the OS implicitly decrypts the hash with the embedded public key and compare it with its computed hash value. If they are not equal, the app was altered. Note that one public key can be used with only one private key.

The sender's or developer's digital certificate contains a public key and other identifying information such as a person's name, a company name, a domain name, and a postal address. The certificate is used to verify that the app is really sent by its developer. The OS verifies the certificate with the trusted Authority (for example, Apple). You can find more details here.

Provisioning

Provisioning is the process of preparing and configuring your app to run on a device and which services your app can access. The provisioning profile is downloaded from your developer account and embedded in the app's bundle. The whole bundle is code-signed. Before the app launches, the OS install the provisioning profile on your device. If the information in the provisioning profile does not match certain criteria, the app won't launch. You can find more details here.

Code signing and provisioning are done in Xcode using the following steps:

1. You must have an Apple developer account, which can be created with Apple ID.
2. Create signing identity and provisioning profile (find more details here)
- Open your project in Xcode and select your project in Project Navigator
- From General tab, fill in information under Identity section. 
3. Connect your device to your Mac machine. From Product menu, select Destination then your device's name. After that, press Command + R to run the project. The app will start on your device.



Wednesday, October 11, 2017

Setting background image for a table view in Swift

The UITableView has backgroundView and backgroundColor properties. You must set backgroundView property to nil if you want to set the background color of the table view. You can set the background view and background color for each cell of the table view too. The cell's backgroundView and backgroundColor properties have higher priorities than the table view's backgroundView and backgroundColor properties.

I'm using Xcode 8 and Swift 3 to develop this demo application. The goal is to set the background image for the table view. I have 3 different background images for different device's screen sizes. We divide the screen sizes into 3 different groups: 1x (for iPhone3G and priors), 2x (for iPhone4, 4s, 5, 5s, 6, 7, 8, and etc.), and 3x (for iPhone 6 Plus, 7 Plus, and 8 Plus). You can find more details here. The reason is the image could lose quality (has jagged edges or looks blur) when it scales down or up. We use one image for one group because there is no much difference between the screen sizes of the devices within the same group. It's ok to scale the image up or down a bit. The background images all have gray background but different texts in the center. The image below shows how it will look like.



1. Create background images for the device's groups

I used Photoshop to create 3 images with resolutions: 1242x2208 pixels (iPhone8 Plus), 750x1334 pixels (iPhone 8), and 320x480 pixels (iPhone 3G). I named them as background@3x.jpg, background@2x.jpg, and background@1x.jpg.

2. Add the background images to the app's bundle

- Open the project in Xcode and double click on Assets.xcassets file to open it in Assets Catalog Document
- Click on the plus sign at the bottom left corner and select New Image Set in the popup.
- Double click on the Image and rename it to background.
- Open Finder and drag the images to the assets catalog in Xcode. 


Note that if you run your application in iPhone 8 plus for example, the system uses 3x image. But if youO run it in iPhone 5 for example, the system use 2x image instead.

3. Set the table view's background image in code

Open the table view controller class and add the following codes to the viewDidLoad() method. In my project, the controller is called ItemViewsController.

let image = UIImage(named: "background", in: nil, compatibleWith: nil)
let imageView = UIImageView(image: image)
tableView.backgroundView = imageView

The background image won't show up because the table view's cell's background color has higher priority. In other words, the cells are in the front. You have to set the cell's background to transparent. It's simple by adding this line of code to the tableView(_:cellForRowAtmethod.

cell.backgroundColor = UIColor.clear







Monday, October 9, 2017

Preventing reordering a row of UITableView

The image below shows how the example application looks like. The last row "No More Items" is not editable though it is by default. (Note that i'm using Xcode 8 and Swift 3)


To prevent a row from being editable, we have to implement two methods: tableView(_:canEditRowAt:) of UITableViewDataSource protocol and tableView(_:targetIndexPathForMoveFromRowAt:toProposedIndexPath) of UITableViewDelegate protocol.

1. Disabling editable style


Implement the method tableView(_:canEditRowAt:) as following:

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        if isLastRowOfSection(tableView, indexPath) {
            return false
        }
        return true

}

When the method tableView(_:canEditRowAt:) returns false, the deletion control (a minus sign in a red circle at the left of the cell) and moving control (gray horizontal bars at the right of the cell) does not show up for that row/cell (the "No More Items" row in this example). Then, the users can't drag that row to move it up or down anymore.

However, they can still move another editable row to the position of this non-editable row and then this non-editable row will automatically move to the position of the editable row. This is the default behavior of the table view control. Please see the image below.



2. Preventing an editable row from moving to the position of non-editable row


Implement the method tableView(_:targetIndexPathForMoveFromRowAt:toProposedIndexPath
as following:
   
    
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
        if isLastRowOfSection(tableView, proposedDestinationIndexPath) {
            return sourceIndexPath
        }
        return proposedDestinationIndexPath
}
    

When the users drag the editable row over the non-editable row (the last row in this example), the non-editable row does not move. It stays where it is. And the editable row moves back to its original position when the users release it.









Thursday, October 5, 2017

Terminating app due to uncaught exception 'CALayerInvalid', reason: 'layer is a part of cycle in its layer tree'

I'm using Xcode 8 and Swift 3 to develop an application for learning purpose. I have one controller and a table view. Everything was working fine at first. But after some silly modification in Interface Builder by a newbie like me, the app crashed when I started it in the simulator. Below is the full log message.

section=0, numOfRows=10
Item: name=Shiny Mac, serialNumber=Optional("2A725A93"), valueInDollars=70, dateCreated=2017-10-05 09:37:41 +0000
row=0, section=0
2017-10-05 16:37:41.594 Homepwner[62092:2091530] *** Terminating app due to uncaught exception 'CALayerInvalid', reason: 'layer <CALayer: 0x600000036240> is a part of cycle in its layer tree'
*** First throw call stack:
(
0   CoreFoundation                      0x0000000102251b0b __exceptionPreprocess + 171
1   libobjc.A.dylib                     0x00000001018a1141 objc_exception_throw + 48
2   CoreFoundation                      0x00000001022ba625 +[NSException raise:format:] + 197
3   QuartzCore                          0x0000000108867ea2 _ZN2CA5Layer30ensure_transaction_recursivelyEPNS_11TransactionE + 102
4   QuartzCore                          0x0000000108867eed _ZN2CA5Layer30ensure_transaction_recursivelyEPNS_11TransactionE + 177
5   QuartzCore                          0x0000000108867eed _ZN2CA5Layer30ensure_transaction_recursivelyEPNS_11TransactionE + 177
6   QuartzCore                          0x0000000108867eed _ZN2CA5Layer30ensure_transaction_recursivelyEPNS_11TransactionE + 177
7   QuartzCore                          0x000000010886fb3a _ZN2CA5Layer15insert_sublayerEPNS_11TransactionEP7CALayerm + 394
8   QuartzCore                          0x000000010886ff50 -[CALayer addSublayer:] + 180
9   UIKit                               0x0000000102e1d7ad -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1322
10  UIKit                               0x000000010311d9a0 -[UITableViewCell _addSubview:positioned:relativeTo:] + 357
11  UIKit                               0x0000000102e0bcf8 -[UIView(Hierarchy) addSubview:] + 838
12  UIKit                               0x000000010323b340 -[UITableViewCellLayoutManager layoutSubviewsOfCell:] + 2386
13  UIKit                               0x000000010326db12 -[UITableViewCellLayoutManagerValue1 layoutSubviewsOfCell:] + 71
14  UIKit                               0x00000001031271d7 -[UITableViewCell layoutSubviews] + 119
15  UIKit                               0x0000000102e2155b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268
16  QuartzCore                          0x0000000108876904 -[CALayer layoutSublayers] + 146
17  QuartzCore                          0x000000010886a526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370
18  UIKit                               0x0000000102e0f334 -[UIView(Hierarchy) layoutBelowIfNeeded] + 1108
19  UIKit                               0x0000000102e1608e +[UIView(Animation) performWithoutAnimation:] + 90
20  UIKit                               0x0000000102ecac29 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 1125
21  UIKit                               0x0000000102ecacf8 -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 74
22  UIKit                               0x0000000102e9f639 -[UITableView _updateVisibleCellsNow:isRecursive:] + 2845
23  UIKit                               0x0000000102ed3ccc -[UITableView _performWithCachedTraitCollection:] + 111
24  UIKit                               0x0000000102ebae7a -[UITableView layoutSubviews] + 233
25  UIKit                               0x0000000102e2155b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268
26  QuartzCore                          0x0000000108876904 -[CALayer layoutSublayers] + 146
27  QuartzCore                          0x000000010886a526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370
28  QuartzCore                          0x000000010886a3a0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
29  QuartzCore                          0x00000001087f9e92 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
30  QuartzCore                          0x0000000108826130 _ZN2CA11Transaction6commitEv + 468
31  QuartzCore                          0x0000000108826b37 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 115
32  CoreFoundation                      0x00000001021f7717 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
33  CoreFoundation                      0x00000001021f7687 __CFRunLoopDoObservers + 391
34  CoreFoundation                      0x00000001021dc038 CFRunLoopRunSpecific + 440
35  UIKit                               0x0000000102d5808f -[UIApplication _run] + 468
36  UIKit                               0x0000000102d5e134 UIApplicationMain + 159
37  Homepwner                           0x00000001012bf8a7 main + 55
38  libdyld.dylib                       0x00000001060b065d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

According to http://blog.benjamin-encz.de/post/ios-9-detects-cycles-in-layout-trees/, I found the problem. It's because I assigned the tableView object to the accessoryView property of the UITableViewCell. I knew that because when I selected the UITableViewCell in Interface Builder and opened the Connection Inspector, I saw the accessoryView is connecting to Table View in Outlets section. Please see the image below.



To understand about Outlets and Referencing Outlets sections, you can go to this post. According to this post, the accessoryView property (an instance of UIView) represents the small area to the right of the content view of a table view's cell. See the image below.


The table view has a cell. The cell has an accessory view. But, I made the table view as the accessory view of its own cell. This does not make sense and make a circular relationship between the table view and its cell.

The solution is deleting the connection between the accessoryView and Table View from the Outlets section.







Understanding Outlets and Referencing Outlets sections in Interface Builder

I'm using Xcode 8 and Swift 3. I created a single view application, in which there was one table view controller (an instance of UITableViewController). Here is how it looks like in the Interface Builder.


By default, there is one outlet and two referencing outlets come with the UITableViewController such as view and dataSource and delegate, respectively. UITableViewController class inherits from UIViewController and so it inherits view property from UIViewController. The view property is of type UIView, but the table view controller initializes it with an object of UITableView named tableView. In the image above, the outlet view is connecting to Table View. Therefore, the Outlets section tells us the inner objects or the properties of the selected object (or control in Interface Builder) are assigned with what objects (or controls).

The UITableView class has dataSource and delegate properties. In Referencing Outlets section, the dataSource and delegate are referencing the Table View control. It means that the Table View, which is the inner control of the Items View Controller control, has two outlets. In other words, the tableView property of the ItemsViewController class has two properties: dataSource and delegate. However from the image above, we can't see which objects were assigned to those two properties. To see it, we have to select on the Table View control and open the Connection Inspector then look under the Outlets section. The image below shows that those two properties were referencing to Items View Controller control or ItemsViewController object.




UITableView does not show all items

I'm using Xcode 8 and Swift 3 to develop an application for learning purpose. The app used UITableView to display a list of items. I had 20 items but only 12 of them were shown in the simulator. I tried to scroll down to the bottom but no more items. Note that I'm using Macbook Air 11'' mid 2013. I tried to scroll in the simulator using 2 fingers as I always did on browsers and other applications.

After that, I found that it was not the problem with my codes. Everything was fine. It's just the behavior of UITableView. It loads only some items to display which can fit the device's screen. It would show more if you use 3-finger scroll. 3-finger scroll in the simulator works the same as 1-finger tap on mobile device.

Saturday, September 30, 2017

UITableView Demo

I'm using Xcode 8 and Swift 3 to build this demo application I got from the book "iOS Programming - The Big Nerd Ranch Guide". The app uses a table view to display a list of items grouped into 2 sections: one for the items worth more than $50 and another one for the rest. The image below shows how it will looks like.



Below is the list of files required by this project:




The main classes involved in this project are:
UITableViewController : retrieves data from the data source to display in the table view
- UITableView : displays the data
- UITableViewDelegate : is a protocol adopted by the delegate of the UITableView object. The table view asks the delegate for information such as row's height and level of indentation and to perform some actions (managing selections and modifying header and footer) for certain events.
- UITableViewCell : is constructed and filled will data by the UITableViewDataSource and then is passed to the table view to create a row.
- UITableViewDataSource : (protocol) is a representative of the data model which provides minimal information about the table view's appearance (such as number of sections and number of rows in each section).


1. Create a single view application in Xcode


- Suppose you already created the a single view application in Xcode named Homepwner. Then in Interface Builder, delete the view controller that Xcode created by default and drag the Table View Controller from the Object Library to the Interface Builder. Here is how it looks like.



- Open Attributes Inspector and check "Is Initial View Controller" under View Controller section.

- Select the white area under the Property Cells and open Attributes Inspector. Then, change the Style in Table View Cell section to Right Detail, which corresponds to UITableViewCellStyle.value1. After that, change the Identifier to UITableViewCell.

- Select the Property Cells and open Attributes Inspector. Then, change the Style in Table View section to Grouped.

After these steps, the view controller would look like following:


2. Create data model classes


The Item class inherits from NSObject. It's because all of UIKit classes such as UIView, UITextField, and UIViewController, inherit either directly or indirectly from NSObject. If at some points you need to interface with the runtime system (or directly call Objective-C functions, which require NSObject), you would be fine. For example, the Objective-C function class_copyPropertyList() accepts NSObject object as its argument (just import this framework ObjectiveC.runtime and then you can call that function in your Swift codes).

ItemStore class is just a helper class so it doesn't have to inherits from NSObject.

Item.swift

import UIKit
class Item: NSObject {
    var name: String
    var valueInDollars: Int
    var serialNumber: String?
    let dateCreated: NSDate
    
    init(_ name: String, _ serialNumber: String?, _ valueInDollars: Int) {
        self.name = name
        self.serialNumber = serialNumber
        self.valueInDollars = valueInDollars
        self.dateCreated = NSDate()
        
        super.init()
    }
    
    convenience init(_ random: Bool = false) {
        if random {
            let adjectives = ["Fluffy", "Rusty", "Shiny"]
            let nouns = ["Bear", "Spork", "Mac"]
            
            let idx = Int(arc4random_uniform(UInt32(adjectives.count)))
            let randomNoun = nouns[idx]
            let randomAdjective = adjectives[idx]
            
            let randomName = "\(randomAdjective) \(randomNoun)"
            let randomSerialNumber = NSUUID().uuidString.components(separatedBy: "-").first!
            let randomValueInDollars = Int(arc4random_uniform(100))
            
            self.init(randomName, randomSerialNumber, randomValueInDollars)
            return
        }
        
        self.init("", nil, 0)
    }
    
    func toString() -> String {
        return "Item: name=\(name), serialNumber=\(String(describing: serialNumber)), valueInDollars=\(valueInDollars), dateCreated=\(dateCreated)"
    }
}

ItemStore.swift 



class ItemStore {
    var allItems = [Item]()
    
    init() {
        createItem(valueInDollars: 20)
        createItem(valueInDollars: 30)
        createItem(valueInDollars: 60)
        createItem(valueInDollars: 70)
        createItem(valueInDollars: 80)
        
    }
    
    func createItem() -> Item {
        let item = Item(true)
        allItems.append(item)
        return item
    }
    
    func createItem(valueInDollars: Int) -> Item {
        let item = Item(true)
        item.valueInDollars = valueInDollars
        allItems.append(item)
        return item
    }
    
    func getGreaterThan50DollarsItems() -> [Item] {
        var items = [Item]()
        for item in allItems {
            if item.valueInDollars > 50 {
                items.append(item)
            }
        }
        return items
    }
    
    func getLessThanOrEqualsTo50DollarsItems() -> [Item] {
        var items = [Item]()
        for item in allItems {
            if item.valueInDollars <= 50 {
                items.append(item)
            }
        }
        return items
    }
}

3. Implement view controller 


3.1 Create ItemsViewController.swift file




import UIKit
class ItemsViewController: UITableViewController {
    
    var itemStore: ItemStore!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let statusBarHeight = UIApplication.shared.statusBarFrame.height
        let insets = UIEdgeInsets(top: statusBarHeight, left: 0, bottom: 0, right: 0)
        tableView.contentInset = insets
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print("\nsection=\(section)")
        
        switch section {
        case 0:
            return itemStore.getGreaterThan50DollarsItems().count
        case 1:
            return itemStore.getLessThanOrEqualsTo50DollarsItems().count
        default:
            return 0
        }
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//        let cell = UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "UITableViewCell")
        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")!
        
        let items: [Item]
        switch indexPath.section {
        case 0:
            items = itemStore.getGreaterThan50DollarsItems()
        case 1:
            items = itemStore.getLessThanOrEqualsTo50DollarsItems()
        default:
            items = [Item]()
        }
        
        let item = items[indexPath.row]
        cell.textLabel?.text = item.name
        cell.detailTextLabel?.text = "$\(item.valueInDollars)"
        
        print("\n\(item.toString())")
        print("\nrow=\(indexPath.row), section=\(indexPath.section)")
        return cell
    }
}

- I overrode the numberOfSections(tableView) method of UITableViewDataSource protocol to tell the table view that there are two sections.

- The method tableView(_:numberOfRowsInSection:) tells the table view the number of rows in each section.

- The method tableView(_:cellForRowAt) constructs a cell with data for a specific row and section then returns it to the table view. Please note that if we have 2 sections and the first section has 3 rows and the second section has 2 rows, this method gets called 5 times. If the first section has 3 rows and the second section does not have a row at all, this method gets called only 3 times.

- Calling this method tableView.dequeueReusableCell(withIdentifier: "UITableViewCell") retrieves the existing UITableViewCell instance from the queue to avoid creating a new one every time the method tableView(_:cellForRowAt) is called. As all cells have the same style, we create it once and reuse it and just change the data. It saves memory. The cell was configured in step 1 and will be created and put in to queue when the application launches.

3.2 Update the table view controller's class in Interface Builder


Open Main.storyboard file and select the table view controller then open the Identity Inspector and change the Class setting under Custom Class section to ItemsViewController.


4. Construct data


Open AppDelegate.swift file and add the following lines to the method application(_:didFinishLaunchingWithOptions)



        var itemStore = ItemStore()
        let itemsController = window!.rootViewController as! ItemsViewController
        itemsController.itemStore = itemStore
        

Constructing data is not the role of ItemsViewController class so I instantiated the ItemStore in AppDelegate.swift and injected it to the itemStore property. This also respects to dependency injection principle. If the way to create the ItemStore changes, we don't have to modify the ItemsViewController class.