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.
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.
AppDelegate.swift
:
unchanged.
Contains three pairs of methods:
launch/terminate,
foreground/background,
active/inactive.
See
Managing
State Transitions
and
Execution
States for Apps.
ViewController.swift
:
unchanged.View.swift
:
added
a
draw(_:)
that calls
draw(at:withAttributes:)
.
Main.storyboard
:
changed the class of the
view
controller’s
UIView
to my class
View
.LaunchScreen.storyboard
:
unchanged.Info.plist
:
the
information
property list
file.
Unchanged.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
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
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.
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
Back in the
Project Navigator,
select the file
View.swift
.
Edit this file in the center window of Xcode.
Hello.zip
onto your Macintosh Desktop.Hello.zip
file on your Desktop to create a folder named
Hello
.
You can now discard
Hello.zip
.Hello
folder,
double-click on the file
Hello.xcodeproj
.
The
Hello
project will open in Xcode.
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
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.
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
.
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
.
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.
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.
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.
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; }
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
.
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.
Info.plist
in the
Project
Navigator
and selecting Open As → Source Code.)
Run the app again.
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
.
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);
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);
"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.
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"
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"
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.
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(); }
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?
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;
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 ];
x
property of class
View
.
At the top of
View.swift
,
after the existing
import
,
insert
import CoreLocation; //for CLLocationDegreesAdd the following property to class
View
immediately after the line that says
class View: UIView {
.
var temperature: Double?; //in Fahrenheit, an optional DoubleInsert 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 °,
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?
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
.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];
drawRect:
but below the
@implementation
line.
- (id) initWithCoder: (NSCoder *) coder { self = [super initWithCoder: coder]; if (self) { // Initialization code self.backgroundColor = [UIColor yellowColor]; } return self; }