UIViewController and UIView class in Xcode Playgrounds
I am trying to figure out how to create a UIViewController in Xcode Playgrounds and attaching a UIView subclass to it.
I am quite new to swift and coding so am having some trouble.
class myView : UIView {
var label : UIButton!
var topHeader : UIImageView!
var nameText : UITextField!
var password : UITextField!
override init(frame: CGRect) {
super.init(frame: CGRect(x: 10, y: 10, width: 350, height: 450))
self.backgroundColor = UIColor.white
self.layer.borderColor = UIColor.black.cgColor
self.layer.borderWidth = 0.5
//place matters what gets loaded in first.
header("")
loginButton()
nameyText()
nameyText2()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init() {
self.init(frame: CGRect.zero)
}
func header(_ imageInsert: String){
let topHeader = UIImageView()
topHeader.frame = CGRect(x: -1, y: -1, width: 400, height: 125)
topHeader.backgroundColor = UIColor.white
topHeader.layer.borderWidth = 0.5
topHeader.translatesAutoresizingMaskIntoConstraints = true
topHeader.image = UIImage(named: imageInsert)
self.addSubview(topHeader)
}
func loginButton (){
//create properties for button
let label = UIButton(type: UIButtonType.roundedRect)
label.backgroundColor = .white
label.frame = CGRect(x: 135, y: 315, width: 75, height: 30)
label.setTitle("Login", for: UIControlState.normal)
label.layer.borderWidth = 0.5
label.layer.borderColor = UIColor.blue.cgColor
label.layer.cornerRadius = 4
self.addSubview(label)
}
func nameyText () {
let nameText = UITextField()
nameText.frame = CGRect(x: 45, y: 200, width: 250, height: 30)
nameText.backgroundColor = .white
nameText.borderStyle = .roundedRect
nameText.layer.borderColor = UIColor.lightGray.cgColor
nameText.placeholder = "Username"
nameText.keyboardAppearance = .default
self.addSubview(nameText)
}
func nameyText2 () {
let password = UITextField()
password.frame = CGRect(x: 45, y: 255, width: 250, height: 30)
password.backgroundColor = .white
password.borderStyle = .roundedRect
password.layer.borderColor = UIColor.lightGray.cgColor
password.placeholder = "Password"
password.keyboardAppearance = .default
password.isSecureTextEntry = true
self.addSubview(password)
}
}
This is the UIView subclass i created which looks like this...
UIView
I then created my main UIViewController class to add it to...
import UIKit
import PlaygroundSupport
public class ViewController : UIViewController, UITextFieldDelegate {
public override func viewDidLoad() {
super.viewDidLoad()
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public override func loadView() {
//trying to make myView the root view for This ViewController
self.view = myView()
}
}
PlaygroundPage.current.liveView = ViewController()
However, when i add my UIView subclass to the UIViewController it looks like this...UIViewController
It's a different size and changing my UIView frame size does nothing.
Would like some help understanding why it does this, thanks.
You need to set the .preferredContentSize of the view controller. You can do this either explicitly, or by overriding it:
public class ViewController : UIViewController, UITextFieldDelegate {
override public var preferredContentSize: CGSize {
get { return self.view.bounds.size }
set { super.preferredContentSize = newValue }
}
public override func viewDidLoad() {
super.viewDidLoad()
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public override func loadView() {
//trying to make myView the root view for This ViewController
self.view = myView()
}
}
Related
So I have a UIBarbuttonItem that I am currently designing based off of a layout that I have done.
import Foundation
import UIKit
class LocationManager: UIBarButtonItem {
var viewController: MainViewController?
lazy var customButton : UIButton = {
let customButton = UIButton(type: .system)
customButton.setImage(UIImage(named: "downArrow"), for: .normal)
customButton.imageEdgeInsets = UIEdgeInsetsMake(0, 20, 0, -10)
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
customButton.semanticContentAttribute = UIApplication.shared
.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
customButton.titleLabel?.font = customFont
customButton.setTitleColor(UIColor.black, for: .normal)
return customButton
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = customButton
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I correctly do the job of using both an image and title and setting the image insets for the button and on load the appearance is great. However, when I leave the screen and come back it seems as though everything is thrown out of wack the image gets moved back and sometimes there will be two images and one will have a distorted size.
Is there anything wrong with my custom button implementation that I am missing.
I have included images for before and after
I suggest you to make your custom button class, then make title and image by adding subviews. In this case UIImageView and UILabel. Because UIButton inherits from UIView you can easy do this. I've never had problems using this way.
Here is the code I've written for you:
import UIKit
class ViewController: UIViewController {
lazy var customButton: CustomButton = {
let button = CustomButton(frame: CGRect(x: 50,
y: 200,
width: view.frame.width - 100,
height: 50))
// This mask for rotation
button.autoresizingMask = [.flexibleLeftMargin,
.flexibleRightMargin,
.flexibleTopMargin,
.flexibleBottomMargin]
button.attrTitleLabel.text = "San Francisco, CA"
button.addTarget(self, action: #selector(chooseCity), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
view.addSubview(customButton)
}
#objc func chooseCity() {
print("Choose city button has pressed")
}
}
class CustomButton: UIButton {
private let arrowImageSize: CGSize = CGSize(width: 20, height: 20)
private let sideOffset: CGFloat = 10
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(attrTitleLabel)
addSubview(arrowImageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var attrTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "NoirPro-SemiBold", size: 20)
label.textColor = .black
return label
}()
lazy var arrowImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "arrow_down")
iv.contentMode = .scaleAspectFit
return iv
}()
override func layoutSubviews() {
super.layoutSubviews()
arrowImageView.frame = CGRect(x: self.frame.width - arrowImageSize.width - sideOffset,
y: self.frame.height/2 - arrowImageSize.height/2,
width: arrowImageSize.width,
height: arrowImageSize.height)
attrTitleLabel.frame = CGRect(x: sideOffset, y: 0, width: self.frame.width - sideOffset*2 - arrowImageSize.width, height: self.frame.height)
}
}
How it looks:
I am working on an application and I just upgraded to Xcode 9 / Swift 4 and also upgraded my iPhone to iOS 11.
The application was installed when I installed iOS 11 and all seemed OK until I run it from Xcode. Now I am stuck with the default NavBar height.
The code I was using to change the height is no longer working:
class CustomNavControllerVC: UINavigationController
{
let navBarHeight : CGFloat = 64.0
let navbarBackButtonColor = UIColor(red: 247/255, green: 179/255, blue: 20/255, alpha: 1)
override func viewDidLoad()
{
super.viewDidLoad()
print("CustomNavControllerVC > viewDidLoad")
}
override func viewDidLayoutSubviews()
{
print("CustomNavControllerVC > viewDidLayoutSubviews")
super.viewDidLayoutSubviews()
navigationBar.frame.size.height = navBarHeight
navigationBar.tintColor = navbarBackButtonColor
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// In my VCs
override func viewDidLoad()
{
customizeNavBar()
}
func customizeNavBar()
{
let navbarBackItem = UIBarButtonItem()
navbarBackItem.title = "Înapoi"
navigationItem.backBarButtonItem = navbarBackItem
let navbarImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 55, height: 20))
navbarImageView.contentMode = .scaleToFill
let navbarLogo = UIImage(named: "NavBarLogo.png")
navbarImageView.image = navbarLogo
navigationItem.titleView = navbarImageView
}
So far the only thing I could find on this issue is this:
iOS 11 navigation bar height customizing
iOS11 customize navigation bar height
How to correctly set UINavigationBar height in iOS 11
But the info provided does not help, unfortunately.
Any ideas / suggestions?
Updated 2017.10.6
I had the same problem. Below is my solution. I assume that height size is 66.
My solution is working fine iOS 10, 11.
Please choose my answer if it helps you.
Create NavgationBar.swift
import UIKit
class NavigationBar: UINavigationBar {
//set NavigationBar's height
var customHeight : CGFloat = 66
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: customHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
frame = CGRect(x: frame.origin.x, y: 0, width: frame.size.width, height: customHeight)
// title position (statusbar height / 2)
setTitleVerticalPositionAdjustment(-10, for: UIBarMetrics.default)
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)
subview.backgroundColor = .yellow
}
stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarContent") {
subview.frame = CGRect(x: subview.frame.origin.x, y: 20, width: subview.frame.width, height: customHeight - 20)
subview.backgroundColor = UIColor(red: 20/255, green: 20/255, blue: 20/255, alpha: 0.4)
}
}
}
}
Set Storyboard
Set Custom NavigationBar class
Add TestView + Set SafeArea
ViewController.swift
import UIKit
class ViewController: UIViewController {
var navbar : UINavigationBar!
#IBOutlet weak var testView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//update NavigationBar's frame
self.navigationController?.navigationBar.sizeToFit()
print("NavigationBar Frame : \(String(describing: self.navigationController!.navigationBar.frame))")
}
//Hide Statusbar
override var prefersStatusBarHidden: Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
//Important!
if #available(iOS 11.0, *) {
//Default NavigationBar Height is 44. Custom NavigationBar Height is 66. So We should set additionalSafeAreaInsets to 66-44 = 22
self.additionalSafeAreaInsets.top = 22
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create BackButton
var backButton: UIBarButtonItem!
let backImage = imageFromText("Back", font: UIFont.systemFont(ofSize: 16), maxWidth: 1000, color:UIColor.white)
backButton = UIBarButtonItem(image: backImage, style: UIBarButtonItemStyle.plain, target: self, action: #selector(SecondViewController.back(_:)))
self.navigationItem.leftBarButtonItem = backButton
self.navigationItem.leftBarButtonItem?.setBackgroundVerticalPositionAdjustment(-10, for: UIBarMetrics.default)
}
override var prefersStatusBarHidden: Bool {
return true
}
#objc func back(_ sender: UITabBarItem){
self.navigationController?.popViewController(animated: true)
}
//Helper Function : Get String CGSize
func sizeOfAttributeString(_ str: NSAttributedString, maxWidth: CGFloat) -> CGSize {
let size = str.boundingRect(with: CGSize(width: maxWidth, height: 1000), options:(NSStringDrawingOptions.usesLineFragmentOrigin), context:nil).size
return size
}
//Helper Function : Convert String to UIImage
func imageFromText(_ text:NSString, font:UIFont, maxWidth:CGFloat, color:UIColor) -> UIImage
{
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = NSLineBreakMode.byWordWrapping
paragraph.alignment = .center // potentially this can be an input param too, but i guess in most use cases we want center align
let attributedString = NSAttributedString(string: text as String, attributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.paragraphStyle:paragraph])
let size = sizeOfAttributeString(attributedString, maxWidth: maxWidth)
UIGraphicsBeginImageContextWithOptions(size, false , 0.0)
attributedString.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Yellow is barbackgroundView. Black opacity is BarContentView.
And I removed BarContentView's backgroundColor.
That's It.
I have made my first custom class in Swift and I want to use it for the text fields in my app. The problem is that it only works for the first textfield in the view controller, the class is not applied to the second one (the second one has no rounded corners, default placeholder color etc.). I have double checked that the right "custom class" is set in the storyboard for both textfields. Why isn't the class applied to both fields? Seems like a thing with a simple solution but I haven't found any...
Here is the class:
import UIKit
class RoundedUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.setAttributes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setAttributes() {
self.layer.cornerRadius = 20.0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = UITextFieldViewMode.always
self.textColor = UIColor.white
self.alpha = 0.7
let str = NSAttributedString(string: (self.placeholder)!, attributes: [NSForegroundColorAttributeName:UIColor.white])
self.attributedPlaceholder = str
}
}
Here is the view controller code:
class LogInViewController: UIViewController {
#IBOutlet weak var password: RoundedUITextField!
#IBOutlet weak var userName: RoundedUITextField!
override func viewDidLoad() {
super.viewDidLoad()
userName.setAttributes()
// Do any additional setup after loading the view.
}
I discovered now that the problem lies in the "setAttributes", I thought the attributes where set in the class but they're actually only set in viewDidLoad for one of them (I put it there in the beginning and forgot to remove it)... I want this to be done in the "init" of the class for all of the fields...
Put your self.setAttributes() in your awakeFromNib method and remove it from init(frame: CGRect)
override func awakeFromNib() {
super.awakeFromNib()
self.setAttributes()
}
your custom class code will be like this
import UIKit
class RoundedUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setAttributes()
}
func setAttributes() {
self.layer.cornerRadius = 20.0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = UITextFieldViewMode.always
self.textColor = UIColor.white
self.alpha = 0.7
let str = NSAttributedString(string: (self.placeholder)!, attributes: [NSForegroundColorAttributeName:UIColor.white])
self.attributedPlaceholder = str
}
}
Hope this helps
I've built a custom UIButton class but I'm struggling to add objects without affecting the behaviour of the button. I've shown the class below. When I use the class in my main code and add a target, the button only works in areas not covered by the image or text. How can I add objects and have the entire button area behave in the same way?
class TestButton: UIButton {
var myText: UITextView!
var myImageView: UIImageView!
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue
let myImage = UIImage(named: "AnImage")
myImageView = UIImageView(image: myImage)
myImageView.frame = CGRect(x: 10, y: 10, width: (myImage?.size.width)!, height: (myImage?.size.height)!)
addSubview(myImageView)
myText = UITextView()
myText.font = UIFont(name: "Courier", size: 12)!
myText.isEditable = false
addSubview(myText)
}
}
A guess here, hope it works.
UIImageView and UITextView does not normally allow "user interaction", meaning that the user can not tap them and expect the app to react based on that. That is probably why, when you tap on the Image view, that event is not passed through to the UIButton below.
Fortunately the fix is easy. You can set the boolean property isUserInteractionEnabled to true and you should be in business again.
So in your case:
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue
let myImage = UIImage(named: "AnImage")
myImageView = UIImageView(image: myImage)
myImageView.frame = CGRect(x: 10, y: 10, width: (myImage?.size.width)!, height: (myImage?.size.height)!)
myImageView.isUserInteractionEnabled = true
addSubview(myImageView)
myText = UITextView()
myText.font = UIFont(name: "Courier", size: 12)!
myText.isEditable = false
myText.isUserInteractionEnabled = true
addSubview(myText)
}
Update (after your comment)
OK so I just tried to create a quick project with your button and...it seems to work, I can click on the imageView and I can click on the label and still get an answer from my function, so there must be some difference in how we've set this up.
Here is my MyButton button, awfully close to the one you've made, the only difference is that I've added a backgroundColor to myImageView, a frame on myText and isUserInteractionEnabled = false on myText
class MyButton: UIButton {
var myText: UITextView!
var myImageView: UIImageView!
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue
let myImage = UIImage(named: "AnImage")
myImageView = UIImageView(image: myImage)
myImageView.frame = CGRect(x: 10, y: 10, width: (myImage?.size.width)!, height: (myImage?.size.height)!)
myImageView.backgroundColor = UIColor.red
addSubview(myImageView)
myText = UITextView()
myText.font = UIFont(name: "Courier", size: 12)!
myText.frame = CGRect(x: 10, y: 80, width: 100, height: 30)
myText.isEditable = false
myText.isUserInteractionEnabled = false
addSubview(myText)
}
}
And here is my ViewController where I use the button
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = MyButton(frame: CGRect(x: 10, y: 100, width: 200, height: 200))
button.myText.text = "Hello"
button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
view.addSubview(button)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func didTapButton(_ sender: MyButton) {
print("It's alive!!")
}
}
Which gives me this pretty UI
And when I tap either the red image, the blue button itself or the "Hello" label I can see this in my console:
It's alive!!
It's alive!!
It's alive!!
So the good news is that it seems to work, now we just need to figure out what the difference between your setup and my setup is :)
Hope that works and helps.
I am having trouble changing the position of my UILabel. I can change font color and background etc but its position doesn't seem to move no matter what I try. Any help would be appreciated. Im also not using storyboard at all.
I'm fairly new to this so I'm probably missing something very obvious. I have googled and tried anything I thought applied but haven't had any luck.
View Builder:
import UIKit
class StandMapView: UIView {
var titleLabel: UILabel = UILabel()
var standMapImage: UIImageView = UIImageView()
var hotspotImage: UIImageView = UIImageView()
var hotspotTitleLabelArray: [UILabel] = []
var hotspotTextArray: [UITextView] = []
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func bind(standMap: StandMap, hotspots: [Hotspot]) {
titleLabel.text = standMap.title
standMapImage.image = UIImage(named: standMap.mapImage)
hotspotImage.image = UIImage(named:standMap.hotspotImage)
for hotspot in hotspots {
let hotspotTitle = UILabel()
let hotspotText = UITextView()
hotspotTitle.text = hotspot.title
hotspotText.text = hotspot.text
hotspotTitleLabelArray.append(hotspotTitle)
hotspotTextArray.append(hotspotText)
}
}
private func setupView() {
let screenWidth = UIScreen.mainScreen().bounds.width
let screenHeight = UIScreen.mainScreen().bounds.height
self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
standMapImage.translatesAutoresizingMaskIntoConstraints = false
hotspotImage.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.blackColor()
titleLabel.sizeToFit()
titleLabel.frame = CGRect(x: screenWidth/2, y: 30, width: 0, height: 0)
titleLabel.textAlignment = .Center
titleLabel.numberOfLines = 0
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.textColor = UIColor.whiteColor()
addSubview(titleLabel)
}
}
View Controller:
import UIKit
class StandMapViewController: UIViewController {
var standMap: StandMap!
var hotspots: [Hotspot] = []
override func viewDidLoad() {
super.viewDidLoad()
Hotspot.all { hotspot in
hotspot.forEach(self.assignHotspotVariable)
}
StandMap.build {standMap in
standMap.forEach(self.assignStandMapVariable)
}
viewForStandMap(standMap, hotspots: hotspots)
}
private func assignStandMapVariable(standMap: StandMap) {
self.standMap = standMap
}
private func assignHotspotVariable(hotspot: Hotspot) {
hotspots.append(hotspot)
}
private func viewForStandMap(standMap: StandMap, hotspots: [Hotspot]) {
let standMapView = StandMapView(frame: CGRectZero)
standMapView.bind(standMap, hotspots: hotspots)
view.addSubview(standMapView)
}
}
If you want to change the position of the label, you need to change the origin x and y
titleLabel.frame.origin.x = 0.0 // put your value
titleLabel.frame.origin.y = 0.0 // put your value
self.view.layoutIfNeeded()
I managed to solve this using snapkit cocoa pod to make the constraints and then adding the subview before declaring these constraints.
Thanks for everyones help.
Heres the changes i made to the setupView function:
private func setupView() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
standMapImage.translatesAutoresizingMaskIntoConstraints = false
hotspotImage.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.blackColor()
titleLabel.textColor = UIColor.whiteColor()
titleLabel.textAlignment = .Center
titleLabel.numberOfLines = 0
titleLabel.adjustsFontSizeToFitWidth = true
addSubview(titleLabel)
titleLabel.snp_makeConstraints { make in
make.topMargin.equalTo(snp_topMargin).multipliedBy(60)
make.centerX.equalTo(snp_centerX)
}
}
If your label has constraints with Autolayout in storyboard, you must disable constraints to move the frame. Try using
titleLabel.translatesAutoresizingMaskIntoConstraints = YES;
Hope this may solve your issue.
If you are using AutoLayout, do following:
set outlet for constraint of UILable that you want to change.
Then change constant of that constraint as per your need.
e.g: xPosOfLable.constant = x