Hello, World!


The view occupies the whole screen; the status bar overlays the top of the view. Left screenshot: the view is white and the upper left corner of the text “Hello, World!” is at the upper left corner of the view. Right screenshot: the view is yellow and the upper left corner of the text is at the lower left corner of the status bar.

Screenshots

To save a .png screenshot of the iOS Simulator on the Mac Desktop,
File → New Screen Shot
and look for the Simulator Screen Shot on your Mac Desktop.

Each screenshot from an iPhone XR simulator is 828 × 1792 pixels, an aspect ratio of approximately .462. I divided the dimensions by 2, shrinking the image to 414 × 896 but keeping the same aspect ratio, and added a thin, black border.

Source code in Hello.zip

  1. AppDelegate.swift: unchanged. Contains three pairs of methods: launch/terminate, foreground/background, active/inactive. See Managing State Transitions and Execution States for Apps.
  2. ViewController.swift: unchanged.
  3. View.swift: added a draw(_:) that calls draw(at:withAttributes:).
  4. Main.storyboard: changed the class of the view controller’s UIView to my class View.
  5. LaunchScreen.storyboard: unchanged.
  6. Info.plist: the information property list file. Unchanged.

Create the project in Xcode 10.1 on macOS Mojave 10.14

Xcode is in the Applications folder on your Mac. Launch Xcode. If Xcode asks you to install additional components, press Install.

Install additional required components?
Xcode requires additional components to support running and debugging. Choose Install to add required components.
Install

Create a new project

File → New → Project…

Choose a template for your new project:
iOS
Application
Single View App
Next

Choose options for your new project:
Product Name: Hello
Team: Mark Meretzky (Personal Team)
Organization Name: John Doe (this goes in the copyright notice in each file)
Organization Identifier: edu.nyu.sps
Bundle Identifier: edu.nyu.sps.Hello
Language: Swift
☐ Use Core Data
☐ Include Unit Tests
☐ Include UI Tests
Next

Save the project on your Mac’s Desktop.
Source Control: ☑ Create Git repository on My Mac
Create

There should now be a new folder named Hello on your Desktop. Since we created a Git repository, the folder should contain a subfolder named .git. To verify this, open the Mac Terminal application and say

cd ~/Desktop
pwd
/Users/myname/Desktop

ls -ld Hello
drwxr-xr-x  5 myname  mygroup  160 Oct 17 14:11 Hello

cd Hello
pwd
/Users/myname/Desktop/Hello

ls -l
drwxr-xr-x  8 myname  mygroup  256 Oct 17 14:22 Hello
drwxr-xr-x@ 5 myname  mygroup  160 Oct 17 14:16 Hello.xcodeproj

ls -la
drwxr-xr-x  12 myname  mygroup   384 Oct 17 14:16 .git
drwxr-xr-x   8 myname  mygroup   256 Oct 17 14:22 Hello
drwxr-xr-x@  5 myname  mygroup   160 Oct 17 14:16 Hello.xcodeproj

Add a new class to the new project.

Create a new Swift class named View. The left panel of Xcode is called the Project Navigator and lists all the folders and files of the project. If you don’t see the Project Navigator, select
View → Navigators → Show Project Navigator
In the Project Navigator, click on the yellow Hello folder to select it.

▼Hello
   ►Hello

Pull down
File → New → File…

Choose a template for your new file:
iOS
Source
Cocoa Touch Class
Next

Choose options for your new file:
Class: View
Subclass of: UIView
☐ Also create XIB file
Language: Swift
Next
Create

The yellow Hello folder in the Project Navigator should now contain a new file named View.swift. I drag View.swift to a position immediately below ViewController.swift, because the view object will be created after the view controller object.

Tell the view controller to create an instance of your new class View.

The view controller object will create a view object that belongs to class UIView or a subclass thereof. We have to tell the view controller that the class should be View.

In the Xcode Project Navigator, select the file Main.storyboard.

▼Hello
   ▼Hello
      Main.storyboard

In the upper left corner of the center panel of Xcode, open the View Controller Scene menu and select the View.

▼View Controller Scene
   ▼View Controller
      ►View

Pull down
View → Inspectors → Show Identity Inspector
Or in the right panel of Xcode, click on the Identity Inspector icon. It’s a rectangle with a smaller rectangle in its upper left corner. (If you don’t see the right panel,
View → Utilities → Show Utilities)
After selecting the Identity Inspector icon,
Custom Class
Class: View
Module: Hello

Edit the new class

Back in the Project Navigator, select the file View.swift. Edit this file in the center window of Xcode.

Create the project from the above Hello.zip file

  1. Control-click on this link: Hello.zip and select
    Save Link As…
    Save Hello.zip onto your Macintosh Desktop.
  2. Double-click on the Hello.zip file on your Desktop to create a folder named Hello. You can now discard Hello.zip.
  3. In the Hello folder, double-click on the file Hello.xcodeproj. The Hello project will open in Xcode.

Run the project

In the Scheme Menu in the upper left corner of Xcode, select an iOS simulator or device. You probably want to select a simulator:
Hello > iPhone XR

Press the triangular Run button in the upper left corner of Xcode. You can resize the simulator by dragging on its lower right corner, or by pulling down
Window → Physizcal Size

Five objects created automatically

Press the triangular Run button in the upper left corner of Xcode to run the app in the iOS Simulator. Five objects are created automatically when the app is launched. The only one of these five objects that is visible on the screen is the view. For the time being, the window is not very important.

  1. The application, which is an object of the class UIApplication created by Apple. (A class created by Apple always starts with two uppercase letters.) The application object contains a property named delegate that refers to the application delegate object or contains nil.

  2. The application delegate, which is an object of the class AppDelegate that we created in the file AppDelegate.swift. (A class that we create ourselves always starts with only one uppercase letter.) The application delegate object contains a property named window (inherited from protocol UIApplicationDelegate) that refers to the window object or contains nil.

  3. The view controller, which is an object of the class ViewController that we created in the file ViewController.swift. The view controller object contains a property named view (inherited from class UIViewController) that refers to the view object.

  4. The window, which is an object of the class UIWindow created by Apple. The window object contains a property named rootViewController that refers to the view controller object or contains nil. It also contains a property named subviews that refers to the view.

  5. The view, which is an object of the class View we created in the file View.swift. The view object contains a property named superview (inherited from class UIView) that refers to the window object. In a later app, we will give the view object a property that refers to the view controller object.

Things to try

  1. Add the following method to class View, immediately before the existing method draw(_:).
    	required init(coder aDecoder: NSCoder) {
    		super.init(coder: aDecoder)!;
    
    		//Background can't be set in draw(_:).
    		backgroundColor = UIColor.yellow;
            }
    

  2. In the draw(_:) method of class View, we can change
    		let point: CGPoint = CGPoint(x: 0.0, y: 0.0);
    
    to
    		let point: CGPoint = CGPoint.zero;
    
    or even to
    		let point: CGPoint = .zero;
    
    CGPoint.zero is a constant, predefined CGPoint structure that always contains 0, 0.

    In fact, we can use CGPoint.zero in place of our variable point as the first argument of draw(at:withAttributes:):

    		s.draw(at: CGPoint.zero, withAttributes: nil);
    
    or
    		s.draw(at: .zero, withAttributes: nil);
    
    See also CGSize.zero and CGRect.zero.


  3. Move the “Hello, World!” text below the status bar. In the draw(_:) method of class View, change the original
    		let point: CGPoint = CGPoint(x: 0.0, y: 0.0);
    
    to the following. The safeAreaLayoutGuide property of class UIView is an object of class UILayoutGuide. safeAreaLayoutGuide therefore contains a property of class CGRect named layoutFrame. This rectangle is the part of the UIView that is not covered by the status bar and the other bars.
    		let point: CGPoint = CGPoint(x: 0.0, y: safeAreaLayoutGuide.layoutFrame.origin.y);
    

    The value of the y property of the origin property of the layoutFrame property of the safeAreaLayoutGuide property of the UIView is 44, which is the height of the status bar in pairs of pixels (at least on iPhone XR in portrait orientation). You can verify this by changing the variable s.

    		let s: String = "Height of status bar is \(safeAreaLayoutGuide.layoutFrame.origin.y) pairs of pixels.";
    
    Height of status bar is 44.0 pairs of pixels.
    

    The height of the status bar is therefore 88 pixels. You can verify this by examining a screenshot in the Preview application.

    Bonus: the y of the safeAreaLayoutGuide will be zero if we hide the status bar. The status bar is hidden automatically in landscape orientation. In the Simulator, pull down
    Hardware → Rotate Right

    We can also hide the status bar in portrait orientation. In the Xcode Project Navigator, select the property list file Info.plist. We will add two properties to this file.

    1. Point at the bottom property (which is probably named “Supported interface orientations”) and two circles will appear, containing a plus sign and a minus sign. Click on the plus sign to create a new property. Click on the left pair of small up and down triangles to select the name “Status bar is initially hidden” for this new property. Click on the right pair to select YES as the value for this property.
    2. Then add one more property, with the name “View controller-based status bar appearance” and the value NO.

    (You can see your two new properties in XML by control-clicking on Info.plist in the Project Navigator and selecting Open As → Source Code.) Run the app again.


    Then recover the status bar by deleting these two properties.

  4. Center the text in the big yellow View. In the draw(_:) method of class View, change the original
    		let point: CGPoint = CGPoint(x: 0.0, y: 0.0);
    
    to the following.
    size(withAttributes:) returns the size of a piece of text.
    bounds.origin is the point at the upper left corner of the big yellow View.
    bounds.size is the size of the big yellow View.
    As always, our point variable will be the location of the upper left corner of the text.
    		let size: CGSize = s.size(withAttributes: nil);
    		let x: CGFloat = bounds.origin.x + (bounds.size.width - size.width) / 2;
    		let y: CGFloat = bounds.origin.y + (bounds.size.height - size.height) / 2;
    		let point: CGPoint = CGPoint(x: x, y: y);
    

  5. The font defaults to a tiny 12-point Helvetica(Neue), and the foreground color defaults to black. Let’s create a dictionary to override these defaults. In the draw(_:) method of class View,
    		let font: UIFont = UIFont.systemFont(ofSize: 36);
    		let foregroundColor: UIColor = UIColor.red;
    
    		//a two-line dictionary
    		let attributes: [NSAttributedString.Key: Any] = [
    			NSAttributedString.Key.font: font,
    			NSAttributedString.Key.foregroundColor: foregroundColor
    		];
    
    Then replace the nil parameter of draw(at:withAttributes:) and size(withAttributes:) with your new attributes. For example,
    		s.draw(at: point, withAttributes: attributes);
    

    You can concoct your own foreground and background colors:

    		//mauve, opaque
    		let foregroundColor: UIColor = UIColor(red: 0.878, green: 0.690, blue: 1.0, alpha: 1.0);
    

  6. Change "Hello, World!" to a string in a foreign character set.
    		let s: String = "هزا مدهش";   //That's amazing!
    

    You can copy and paste the above text. Or to type it yourself, click on a point within the double quotes, and then pull down
    Edit → Emoji & Symbols…
    Click on the rectangular icon in the upper right corner if necessary to expand the Emoji & Symbols window. Then pull down the gear in the upper left corner, select Customize List…, and open Middle Eastern Scripts.


  7. Instead of the inane text “Hello, World!”, put something useful into the string. How about one of the properties of the device? In the draw(_:) method of class View, change
    		let s: String = "Hello, World!";
    
    to one of the following.
    		//There is always exactly one object of class UIDevice.
    		//The object is therefore a singleton.
    		let device: UIDevice = UIDevice.current;
    
    		let s: String = device.model;         //"iPhone", "iPad", "iPod touch"
    		let s: String = device.identifierForVendor!.uuidString;	//serial number, e.g., "F976349D-7E34-4816-8EEA-CABC2ACB84E9"
    		let s: String = device.systemName;    //name of operating system, e.g. "iPhone OS"
    		let s: String = device.systemVersion; //version number of operating system, e.g., "12.2"
    

  8. Print the current price of IBM. In the draw(_:) method of class View, change
    		let s: String = "Hello, World!";
    
    to the following. The format sl1t1d1 is the concatenation of s (lowercase S), l1 (lowercase L one), t1 (lowercase T one), d1 (lowercase D one); see the list of Yahoo formats. The init(string:) method of class URL indicates an error by returning nil. The init(contentsOf:encoding: method of class String indicates an error by throwing an Error. See Catching and Handling an Error.
    		var s: String;
    		let url: URL? = URL(string: "http://finance.yahoo.com/d/quotes.csv?s=IBM&f=sl1t1d1");
    
    		if url == nil {
    			s = "malformed URL";
    		} else {
    			do {
    				s = try String(contentsOf: url!, encoding: String.Encoding.utf8);
    			} catch {
    				s = String(describing: error);   //error is an Error.
    			}
    		}
    

    The Xcode Debug Area will display the following error:

    2017-10-21 08:41:54.792792-0400 Hello[1789:47359] App Transport Security
    has blocked a cleartext HTTP (http://) resource load since it is insecure.
    Temporary exceptions can be configured via your app's Info.plist file.
    2017-10-21 08:41:54.792961-0400 Hello[1789:47361] NSURLConnection finished
    with error - code -1022
    

    and the app will display the following. Switch to a smaller font, or rotate the iOS Simulator to landscape orientation:
    Hardware → Rotate Right

    Error Domain=NSCocoaErrorDomain Code=256
    "The file “quotes.csv” couldn’t be opened."
    UserInfo={NSURL=http://finance.yahoo.com/d/quotes.csv?s=IBM&f=sl1t1d1}
    

    You will also have to add the App Transport Security property to the Info.plist file. Since the value of this property is an entire dictionary, not merely a YES or a NO, the easiest way to add it is to control-click on Info.plist in the Project Navigator, select Open As → Source Code, and insert the following property immediately before the

    </dict>
    </plist>
    
    at the bottom of the file.

    	<key>NSAppTransportSecurity</key>
    	<dict>
    		<key>NSAllowsArbitraryLoads</key>
    		<false/>
    		<key>NSExceptionDomains</key>
    		<dict>
    			<key>finance.yahoo.com</key>
    			<dict>
    				<key>NSIncludesSubdomains</key>
    				<true/>
    				<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
    				<true/>
    				<key>NSTemporaryExceptionMinimumTLSVersion</key>
    				<string>TLSv1.1</string>
    			</dict>
    		</dict>
    	</dict>
    
    To take a screenshot of part of the Xcode window, see How to take a screenshot of a selected portion of your screen.

    The time zone is Eastern:

    "IBM",162.07,"4:01pm","10/20/2017"
    

  9. Print the orientation of the device. In the draw(_:) method of class View, change
    		let s: String = "Hello, World!";
    
    to the following.
    		//an array of 7 Strings, indexed by a UIDeviceOrientation
    
    		let orientations: [String] = [
    			"Unknown",
    			"Portrait",		//home button at bottom
    			"PortraitUpsideDown",	//home button at top
    			"LandscapeLeft",	//home button on right
    			"LandscapeRight",	//home button on left
    			"FaceUp",
    			"FaceDown"
    		];
    
    		let device: UIDevice = UIDevice.current;
    		let orientation: UIDeviceOrientation = device.orientation;
    		let s: String = orientations[orientation.rawValue];
    
    Even better, let the above constants orientations and device be stored properties of class View. To get to PortraitUpsideDown, see the ViewController example.

  10. Print the current date and time in GMT and local time. In the draw(_:) method of class View, change
    		let s: String = "Hello, World!";
    
    to the following.
    		let date: Date = Date();	  //holds the current date and time
    		let s: String = date.description; //or date.description(with: .current);
    

    Run the app and note that the time is updated whenever you change the orientation of the device from portarit to landscape and back again.

    2018-10-28 12:49:24 +0000
    
    Sunday, October 28, 2018 at 8:49:24 AM Eastern Daylight Time
    

    Then append the following statements to draw(_:) and run the app again. The code enclosed in the {curly braces} is a closure.

    		//Call draw(_:) once per second.  One second equals 1000 milliseconds.
    
    		let oneSecond: DispatchTimeInterval = DispatchTimeInterval.milliseconds(1000);
    		let now: DispatchTime = DispatchTime.now();
    		let oneSecondFromNow: DispatchTime = now + oneSecond;
    
    		//Call the setNeedsDisplay method of this View object one second from now.
    		//setNeedsDisplay will trigger a call to the draw(_:) method of this View object.
    
    		DispatchQueue.main.asyncAfter(deadline: oneSecondFromNow) {
    			self.setNeedsDisplay();
    		}
    

  11. Move the text down the screen as the app runs. Call draw(_:) once every 33 milliseconds, i.e., approximately 30 times per second.
    		//Call draw(_:) every 33 milliseconds.
    		let interval: DispatchTimeInterval = DispatchTimeInterval.milliseconds(33);
    		DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + interval) {
    			self.setNeedsDisplay();
    		}
    
    Add the following stored property to class View. To make it a stored property, insert it immediately after the line that says class View: UIView {. You’ll have to remove the y variable you added in the above exercise 3.
    	//the y coordinate of the point where the text will be printed
    	var y: CGFloat = 0.0;
    
    Change the CGPoint to
    		let point: CGPoint = CGPoint(x: 0.0, y: y);
    
    And just before calling asyncAfter(deadline:), add 1 to y:
    		y += 1;	//means y = y + 1;
    
    Can you get the text to move from left to right? How about diagonally?

  12. Make the text bob up and down. Call draw(_:) every 33 milliseconds. Replace the y property of class View with the following property.
    	//Increases at the rate of 2 * pi per 3 seconds.
    	var x: Float = 0;
    
    Change the CGPoint to
    		let point: CGPoint = CGPoint(x: 0.0, y: bounds.size.height / 2 + CGFloat(sin(x)) * bounds.size.height / 4);
    
    Change the name of the stored property from y to x, and change the y += 1; to
    		x += 2 * CGFloat.pi * 33 / 1000;
    

  13. Pulse the text. Keep the above property x of class View. Get rid of the bobbing by going back to the original CGPoint of plain old constant 0, 0. Change the dictionary of attributes to the following. The return value of sin must be converted to the data type CGFloat demanded by the init method of class UIColor.
    		//alpha goes from 0 to 1 and back again every 3 seconds.
    		let foregroundColor: UIColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5 + CGFloat(sin(x)) / 2);
    
    		let attributes: [NSAttributedStringKey: Any] = [
    			NSAttributedStringKey.font: font,
    			NSAttributedStringKey.foregroundColor: foregroundColor
    		];
    

  14. Display the current temperature of your location. Get rid of the pulsing and remove the x property of class View. At the top of View.swift, after the existing import, insert
    import CoreLocation;	//for CLLocationDegrees
    
    Add the following property to class View immediately after the line that says class View: UIView {.
    	var temperature: Double?;	//in Fahrenheit, an optional Double
    
    Insert the following code into the init(coder:) method of class View after setting the background color.
    		let apiKey: String = "92a0b81218fc51a38a717d1aed2b4a22";
    
    		//New York City
    		let  latitude: CLLocationDegrees =  40.78;	//north latitude is positive
    		let longitude: CLLocationDegrees = -73.97;	//west longitude is negative
    
    		let url: URL? = URL(string: "https://api.forecast.io/forecast/\(apiKey)/\(latitude),\(longitude)");
    		if url == nil {
    			print("could not create URL");
    			return;
    		}
    
    		let sharedSession: URLSession = URLSession.shared;
    
    		let downloadTask: URLSessionDownloadTask = sharedSession.downloadTask(
    			with: url!,
    			completionHandler: {(filename: URL?, response: URLResponse?, error: Error?) -> Void in
    
    				if (error != nil) {
    					print("could not download data from server: \(error!)");
    					return;
    				}
    
    				//Arrive here when the data from the forecast server has been
    				//downloaded into a file in the device.
    
    				//Copy the data from the file into a Data object.
    
    				let data: Data;
    				do {
    					data = try Data(contentsOf: filename!);
    				} catch {
    					print("could not create Data object");
    					return;
    				}
    
    				//The data is in JSON format.
    				//Copy the data from the Data object into a big Swift dictionary.
    				let dictionary: [String: Any]?;
    				do {
    					try dictionary = JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
    						as? [String: Any];
    				} catch {
    					print("could not create big dictionary: \(error)");
    					return;
    				}
    
    				//The big dictionary contains a smaller one.  Let's name it "currently".
    				let currently: [String: Any]? = dictionary!["currently"] as? [String: Any];
    				if currently == nil {
    					print("big dictionary does not contain the key \"currently\"");
    					return;
    				}
    
    				//The "currently" dictionary contains the current temperature.
    				self.temperature = currently!["temperature"] as? Double;
    				if self.temperature == nil {
    					print("The \"currently\" dictionary does not contain the key \"temperature\"");
    					return;
    				}
    
    				//Now that we have the temperature, call draw(_:).
    
    				DispatchQueue.main.async(execute: {() -> Void in
    					self.setNeedsDisplay();
    				});
    			});
    
    		downloadTask.resume();
    
    In draw(_:), create the string s as follows. To type the degree sign °,
    Edit → Emoji & Symbols → Punctutation
    Left justify the string as in exercise 2 above.
    		var s: String;
    		if temperature == nil {
    			s = "temperature";	//haven't heard from the server yet
    		} else {
    			s = "temperature \(temperature!)° F";
    		}
    
    Remove the call to async(execute:). How many times is draw(_:) called? How long is the interval between the calls?

  15. To write the project in the language Objective-C, here’s what you’ll have to do differently.
    1. When creating the project, select Language: Objective-C.
    2. Instead of dragging the file View.swift to a position immediately below ViewController.swift, drag the two files View.h and View.m to a position immediately below ViewController.m.
    3. In the View’s Identity Inspector, the class should be View but the Module should be the empty string.
    4. In the file View.m, uncomment the method drawRect: and paste the following code under the “Drawing code” comment.
      	NSString *s = @"Hello, World!";
      	CGPoint point = CGPoint(x: 0.0, y: 0.0);
      	UIFont *font = [UIFont systemFontOfSize: 32.0];
      
      	NSDictionary *attributes =
      		[NSDictionary dictionaryWithObject: font forKey: NSFontAttributeName];
      
      	[s drawAtPoint: point withAttributes: attributes];
      
    5. Paste the following method above drawRect: but below the @implementation line.
      - (id) initWithCoder: (NSCoder *) coder {
      	self = [super initWithCoder: coder];
      	if (self) {
      		// Initialization code
      		self.backgroundColor = [UIColor yellowColor];
      	}
      	return self;
      }