import Foundation import UIKit // HW3 due Wednesday. // Office hours tomorrow (me) and Wednesday (Sally) // HW2 grades sometime this week (no guarantees) // HW4 coming out Wednesday at midnight—-will have 7 days to do it. // // Lecture notes coming out soon, preliminary Playground coming // out tonight. Lecture notes will be out no later than HW4 is // announced. // // How *is* HW3 going? This is usually the homework where we lose // extreme amounts of students? Is it difficult? Have we been // helpful enough? You know the deal by now. // Today, we're going to build a user interface from scratch. // It's actually a VERY beautiful looking app *show picture**. // Some of you may find this lecture inspiring. And some of you // leave this room thinking that Storyboards are God's gift // to iOS development. Either way, we're going to give you a taste // of what it's like to build user interfaces from scratch. // // So, open up a new Xcode project. Call it 'Hipstr', and make // it a Single View Application. Then, delete Main.storyboard // and LaunchScreen.xib and ViewController.swift. They are most // definitely not needed here today. Next, go to Supporting Files > // Info.plist and delete the two entries in this plist relating // to Storyboard and LaunchScreen. // // Finally, you might notice a warning pop up, which wants to add // Default-568h@2x.png. This is because Xcode, without Storyboards, // doesn't know how to handle the native resolutions of bigger // phones such as the iPhone 6 and 6 Plus--having this .png on // hand, which is helpfully provided by Xcode--allows your project // to display on bigger screens. // // As your app stands right now, it has NO visuals whatsoever. // It will be a black screen if you run it. Let's give your // app at least a white screen to work with. In your // AppDelegate.swift, you should modify the following function in // the following class: class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { self.window = UIWindow(frame: UIScreen.mainScreen().bounds) self.window?.backgroundColor = UIColor.whiteColor() self.window?.makeKeyAndVisible() // Override point for customization after application launch. return true } // other functions down here... } // AppDelegate contains user defined functionality about how // your app should work on the very high level. So, when our app is // just about loaded and launched and ready to get, the above // function is called. In this function, we make a window (of class // UIWindow) and set that to be the main window of your screen. // It's not too important what this means--just that we now have // a place to put views in, and it's a canvas the size of the // iPhone screen. We set the background to be white and then we set // it to display. Boom. We have a white screen now. At this point, // there is NO CODE controlling what is happening on screen right // now. // Now, we can add a ViewController to our project. Cmd+Shift+N // and make a subclass of UIViewController--call it // MainViewController. Now, in our MainViewController, let's write: class MainViewControler: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.purpleColor() } } // Now, we need to tell our UIWindow that we should start our app // off by passing control to MainViewController. And, all that // MainViewController will do is change its view's background to // purple. So, in our AppDelegate, before we call the function /////////////////////////////////////////////////////////////////// //// self.window?.makeKeyAndVisible() /////////////////////////////////////////////////////////////////// // We must add the following line: /////////////////////////////////////////////////////////////////// //// self.window?.rootViewController = MainViewController() /////////////////////////////////////////////////////////////////// // And this tells our UIWindow to pass control over to // MainViewController to handle things. If this works, your screen // should be purple now. If not...see me after class (or call Sally // to help). But for now...we're going to make a transition. // Let's talk about design patterns. Throughout this class, you're // going to learn about a few. Last week we learned about the // Delegation pattern. This week, we're learning about the Factory // pattern. // // The Factory pattern is pretty simple: a Factory is a class which // produces some type of object. Often times you will pass certain // options to the Factory class and it will make your object // exactly to your specification. Factory classes are usually // classes with no initialization and all class funcs. Let's make // an example. Make a new class, call it ButtonFactory, subclass it // from NSObject, and let's get to work on writing a Button // Factory. First, let's make an enum to define the types of // buttons that our Factory will support: enum ButtonType { case LoginButton case RegisterButton } // Now, let's write the code to spit out a button based on the // button type, and also based on the text requested inside the // button. It's a lot all at once, so I'll try to explain the code // as I go: class ButtonFactory: NSObject { class func buttonWithType(type: ButtonType, title: String) -> UIButton { let button = UIButton.buttonWithType(UIButtonType.Custom) as UIButton button.setTitle(title, forState: UIControlState.Normal) button.setTitleColor(UIColor.whiteColor(), forState: .Normal) button.titleLabel!.font = UIFont.systemFontOfSize(26) switch type { case .LoginButton: button.backgroundColor = UIColor(red: 30.0/255.0, green: 185.0/255.0, blue: 60.0/255.0, alpha: 0.85) break case .RegisterButton: button.backgroundColor = UIColor(red: 68.0/255.0, green: 140.0/255.0, blue: 203.0/255.0, alpha: 0.85) break default: break } // let buttonRect : CGRect = CGRect(origin: CGPointZero, // size: CGSize(width: 250.0, // height: 44.0)) // button.frame = buttonRect return button } } // Discuss in depth: class funcs (functions on classes, // not objects--don't need to make an instance of a class); control // states--the main ones are Normal, Selected, and Disabled, but // there are more. Everything else is just code versions of stuff // we do in IB. // // In fact, you should try to find as many parallels as possible // between this code and the things we do in IB--it might help. // // So now, let's add this to our MainViewController. In our // MainViewController's viewDidLoad() function, let's rewrite it as class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let loginButton = ButtonFactory.buttonWithType(.LoginButton, title: "Login!") self.view.addSubview(loginButton) } } // Now we run the code and...uh-oh! Where did the button go? Can // anyone figure out why? // // We never specified the button's size! It defaulted to (0, 0) as // its (width, height). Not only that, but we also never set its // location on the screen. Problem. But, this is a perfect segue // into bounds and frames. I suggest everyone pay attention since // this is very important iOS fundamentals. // // I won't talk about bounds and frames in this Playground, since // everyone should be paying attention. :P I'll update the website // later with information. // // ... // // So, now we know we need to update the frame of our button. // So, to do that, we need to talk about some types that have been // historically used in iOS (or rather, Mac development). We have // // CGFloat - a float (or double, depending on your architecture) // CGPoint - a pair of CGFloats, representing am (x, y) point // CGSize - a pair of CGFloats, representing (width, height). // CGRect - a CGPoint representing the top left corner of a rect, // and a CGSize representing the size of a rect. // // The frame of a button is clearly a CGRect, so we need 4 floats // to describe this button--two for the origin of the button, and // two for the size. We say: let buttonRect: CGRect = CGRect(origin: CGPointZero, size: CGSize(width: 250.0, height: 44.0)) // This is because we're not sure where the button should be put, // but the Factory knows how big the buttons should be. Now, don't // forget to /////////////////////////////////////////////////////////////////// //// button.frame = buttonRect /////////////////////////////////////////////////////////////////// // Which should happen before you 'return button'. // // Great, so let's run the code. Okay, great, it's all up in the left // corner of the screen--but it *DOES* display. Not too shabby. We can change // the location of this button in our viewDidLoad (since our ButtonFactory // probably shouldn't know where to place things, but our MainViewController // kinda should). // // First, we need to know more information about our screen before we can // do anything with placing our objects. In our viewDidLoad(), let's add the // line let screenBounds = UIScreen.mainScreen().bounds // These are the BOUNDS of the screen itself. So, let's set the x-coordinate // of the button to be centered, and the y-coordinate to be always 30% from the // bottom of the screen. Then in our viewDidLoad, we'd say (before we add the // subview): /////////////////////////////////////////////////////////////////////////////// //// loginButton.center = CGPoint(x: CGRectGetMidX(screenBounds), //// y: CGRectGetMaxY(screenBounds) * 0.7) /////////////////////////////////////////////////////////////////////////////// // And now our view displays! Now, let's add an almost identical Register // button at 15% from the bottom. We'd simply add let registerButton = ButtonFactory.buttonWithType(.RegisterButton, title: "Register!") registerButton.center = CGPoint(x: CGRectGetMidX(screenBounds), y: CGRectGetMaxY(screenBounds) * 0.85) // And make sure you add it to your view! // // Looks good now! Making progress. Let's talk about layers now. What exactly // is a UIView concerned with? Obviously, it's concerned with displaying // things, because that's what makes it a view. But what else is it concerned // with? Grabbing actions from the touch screen, who has control of the screen // (e.g. in case of text box), who's relative to who...it turns out, the UIView // is an aggregate class made up of a lot of different things that handle all // the different tasks that a UIView must handle. // // One of those tasks, of course, is displaying. What's the class inside a // UIView that's concerned with displaying? It's called a CALayer. // // Aside: does anyone know what a CG and CA mean? Bonus points. // // So, the CALayer is the part of a UIView that is SOLELY concerned with // drawing. Nothing else! So, since it's solely concerned with drawing, it // has some pretty powerful capabilities in terms of what it can and can't do // with what it displays. Often times, the CALayer inside a UIView has // capabilities that the UIView doesn't. This is mostly so we don't muddle // down the commonly used UIView class down with the hardcore technical // details of a CALayer class. So, to do some of the hardccore technical things // onto a view, you must access its layer. For example, in our Button Factory, // we can modify our buttons with the line: /////////////////////////////////////////////////////////////////// //// button.layer.cornerRadius = 4.0; /////////////////////////////////////////////////////////////////// // And now our corners are rounded! Play with that as much as you like. There // are a lot of really cool and really powerful tools you can mess around with // in the layer. This includes border radius, shadow offest, shadow color, // shadow radius, border color, etc. // // One small caveat about UIColors and CALayers // // Cool, now let's add our picture into the mix. Notice that the picture was // made in Photoshop (a few years ago, to be sure), so it is designed to fit // the screen perfectly. We can simply say: let backgroundImage = UIImage(named: "bench.png") let backgroundImageView = UIImageView(image: backgroundImage) // Add the image view (FIRST) to your subview list and voila! Looks great. // However, your insane boss who claims to have a PhD in UIs comes in and says // that the background image is way too bright and distracting--it should be // toned down with some darkness on the screen. // // Unfortunately, your Photoshop license expired, so you can't touch up the // image! What do we do now? Add a dark overlay to the image: let darkOverlay = UIView(frame: screenBounds) darkOverlay.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.15) // Now add that subview in between the background image and everything else. // Now your boss can shut the hell up. // // FINALLY (woo-hoo) let's add the Hipstr label. We have a custom font going // on here called Impregnable, which is funny because Tyler totally stole it // from somewhere. Either way, we want to make a label, make it really big, // set its font to be the Impregnable font, make it white, and make it say // "Hipstr". // // We can do it like this: let titleLabel = UILabel(frame: CGRectZero) titleLabel.text = "Hipstr"; titleLabel.font = UIFont(name: "ImpregnablePersonalUseOnly", size: 100) titleLabel.textColor = UIColor.whiteColor() titleLabel.layer.shadowColor = UIColor.darkGrayColor().CGColor titleLabel.layer.shadowOffset = CGSize(width: 1.0, height: 1.0) titleLabel.layer.shadowRadius = 3.0 titleLabel.layer.shadowOpacity = 1.0; titleLabel.sizeToFit() titleLabel.textAlignment = NSTextAlignment.Center titleLabel.center = CGPoint(x: CGRectGetMidX(screenBounds), y: CGRectGetMaxY(screenBounds) * 0.15) // That was a LOT, but we did it. Note that, in order to use a custom font, // you have to take a few steps to do so. There's a link somewhere in the world // on how to do it, but you basically need to register your font in your // Info.plist, and then find its name. I'll post the link online. // If you're confused on, for example, what shadowOpacity is (or maybe more // importantly, what that 1.0 means), remember you can Cmd+Click or Option-Click // on these things to find out more information. // Now the app is done. Pick two out of the following list of things and // implement them: // 1. Change the buttons to react when they're pressed (e.g. get darker). // 2. Change the status bar (that shows the time, WiFi, etc) and make it // either disappear or turn white. // 3. Find another image online and replace the bench.png with it--maybe even // change the color scheme of the buttons. // 4. Make the app work in landscape mode! I'll actually be doing this on the // main screen, so if you're interested in doing this, you should watch me // do it (this is one less thing you have to Google since I'm telling you // how to do it). // TODO IT: override func viewDidLoad() { super.viewDidLoad() self.initInterface() } override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) { for subview in self.view.subviews { subview.removeFromSuperview() } self.initInterface() for subview in self.view.subviews { if let imageView: UIImageView = subview as? UIImageView { if (fromInterfaceOrientation.isPortrait) { imageView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI / 2.0)) imageView.frame = CGRect(origin: CGPointZero, size: imageView.frame.size) } } } }