Changing UISwitch width and height - ios

I am trying to change the default height and width of a UISwitch element in iOS, but unsuccessfully.
Can you change the default height and width of a UISwitch element?
Should the element be created programmatically?

I tested the theory and it appears that you can use a scale transform to increase the size of the UISwitch
UISwitch *aSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(120, 120, 51, 31)];
aSwitch.transform = CGAffineTransformMakeScale(2.0, 2.0);
[self.view addSubview:aSwitch];

Swift 4
#IBOutlet weak var switchDemo: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
switchDemo.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
}

Swift 5:
import UIKit
extension UISwitch {
func set(width: CGFloat, height: CGFloat) {
let standardHeight: CGFloat = 31
let standardWidth: CGFloat = 51
let heightRatio = height / standardHeight
let widthRatio = width / standardWidth
transform = CGAffineTransform(scaleX: widthRatio, y: heightRatio)
}
}

Not possible. A UISwitch has a locked intrinsic height of 51 x 31 .
You can force constraints on the switch at design time in the xib...
but come runtime it will snap back to its intrinsic size.
You can supply another image via the .onImage / .offImage properties but again from the docs.
The size of this image must be less than or equal to 77 points wide
and 27 points tall. If you specify larger images, the edges may be
clipped.
You are going to have to bake your own custom one if you want another size.

here is a nice UISwitch subclass that i wrote for this purpose, its also IBDesignable so you can control it from your Storyboard / xib
#IBDesignable class BigSwitch: UISwitch {
#IBInspectable var scale : CGFloat = 1{
didSet{
setup()
}
}
//from storyboard
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
//from code
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
private func setup(){
self.transform = CGAffineTransform(scaleX: scale, y: scale)
}
override func prepareForInterfaceBuilder() {
setup()
super.prepareForInterfaceBuilder()
}
}

import UIKit
extension UISwitch {
static let standardHeight: CGFloat = 31
static let standardWidth: CGFloat = 51
#IBInspectable var width: CGFloat {
set {
set(width: newValue, height: height)
}
get {
frame.width
}
}
#IBInspectable var height: CGFloat {
set {
set(width: width, height: newValue)
}
get {
frame.height
}
}
func set(width: CGFloat, height: CGFloat) {
let heightRatio = height / UISwitch.standardHeight
let widthRatio = width / UISwitch.standardWidth
transform = CGAffineTransform(scaleX: widthRatio, y: heightRatio)
}
}

Even if it’s possible to make a UISwitch smaller, this would negatively effect the user experience. Apple's Human Interface Guidelines recommend a minimum size of 44 points for touch targets.
Provide ample touch targets for interactive elements. Try to maintain a minimum tappable area of 44pt x 44pt for all controls
By scaling this to smaller than the standard size, it will become more difficult for users to tap, and also introduce accessibility concerns. Please consider users with less than perfect vision or motor control before making UI elements small.
Finally, here’s an excerpt from a great article about touch target sizes illustrating what can happen when controls are too small.
Interviewer — “I noticed, you had some trouble submitting your email address on this screen, can you tell me how that felt?”
User — “Oh yeah, I’m not very good at technology.”
Interviewer — “What do you think was causing you to struggle at that point?”
User — “The buttons were hard to tap, and I just kept stuffing it up.”

Related

UITextField shrinks text before necessary

I have a UITextField for which I've set autoAdjustFontSizeToFitWidth to true and minimumFontSize to 0. The problem is the setting shrinks the text noticeably sooner than it really should. For example, here is an image of a UITextField with the above settings:
The green is the background color of the UITextField. In this example, the text has not shrunk yet, but no matter what I type as the next character the text field always begins shrinking; despite clearly being enough room on the left side for a few more characters. Here is another image with additional characters entered:
As you can see, there is a relatively large area on the left side that the text field won't place text in when auto adjusting. This is for a right aligned text field. The same can be said of center aligned text fields as well, where there is space on the left and right that seems as if an auto adjusting text field won't place text inside.
How do I get it so that auto adjusting text fields use the entire available space?
Update:
You can do the text calculations and font resizing manually. By doing so you will avoid hacks and future compatibility issues.
A simple implementation looks like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var originalFont: UIFont!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.font = self.textField.font?.withSize(44)
self.textField.adjustsFontSizeToFitWidth = false
self.originalFont = textField.font
self.textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
#objc
func textFieldDidChange(sender: UITextField) {
let textRect = sender.textRect(forBounds: sender.bounds)
let textWidth = textRect.width
var currentFont = self.originalFont!
var i = 0
while (i < 10) {
let unrestrictedTextWidth = sender.text!.boundingRect(with: CGSize(width: .greatestFiniteMagnitude,
height: textRect.height),
attributes: [.font : currentFont],
context: nil).width
if unrestrictedTextWidth <= textWidth {
break
}
let factor = textWidth / max(textWidth, unrestrictedTextWidth)
let originalSize = currentFont.pointSize
currentFont = self.originalFont!.withSize(originalSize * factor)
i += 1
}
sender.font = currentFont
}
}
Interestingly the actual relationship between text rect and font size is non-linear and non-trivial. So I added multiple iteration steps to approximate the correct size. I chose a maximum of 10 iterations to avoid infinite loops on very small sizes and rounding errors.
Original Answer:
There has always been some magic around UITextField and adjustsFontSizeToFitWidth. See for example this post from 2015 about how the initial font size affects the minimum font size:
https://stackoverflow.com/a/30881385/921573
A UITextField with:
Font size 17, minimum size 15 will go down to 15 if need be
Font size 17, minimum size 10 will only go down to 14
Font size 13, minimum size 4 will stay at 13
In my tests, setting the minimum font size in IB to 0 just gets ignored – in order so see the shrinking effect it has to be a small value like 1.
Setting it in code to 0 works fine.
So I think it is safe to say that UITextField might be considered historically buggy when it comes to adjustsFontSizeToFitWidth.
That being said, I found a workaround for you:
class FixedTextField: UITextField {
override func textRect(forBounds bounds: CGRect) -> CGRect {
let magicNumber = -15.0
if self.adjustsFontSizeToFitWidth {
return CGRect(
x: bounds.origin.x + magicNumber,
y: bounds.origin.y,
width: bounds.size.width - magicNumber,
height: bounds.size.height
)
} else {
return super.textRect(forBounds: bounds)
}
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return self.textRect(forBounds: bounds)
}
}
This custom text field uses countermagic to mitigate the issue.
You may have to play with the magicNumber according to your font or dimensions or device. For me 15 works ok:
This works for me and the textField.textAlignment is set to .right (it will depend on how many characters you put in the textField though) :
class TextFieldOne: UITextField {
override func alignmentRect(forFrame frame: CGRect) -> CGRect {
// let newWidth = frame.width + 10 // if you want to reduce the right side too.
let x = frame.origin.x - 15 // suit yourself here
let newFrame = CGRect(x: x, y: frame.origin.y, width: frame.width, height: frame.height)
return newFrame
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return self.alignmentRect(forFrame: self.bounds)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return self.alignmentRect(forFrame: self.bounds)
}
}

How to programmatically lay out stacking views and rotate?

I'd like to layout 3 imageViews programmatically by setting the frames manually. I am able to set up the middle image just fine but when trying to rotate the other two, the images's frames distort when using CGAffineTransform(rotatedBy:)
Right now I am setting the center image based on the center of the view and using that as a reference point for the left and right images. Any suggestions to rotate the images or if there's anything glaringly wrong with my approach? I suspect the issue has to do with the rotating point when using CGAffineTransform. I read around and folks say it's defaulted at (0,0) and I believe I want to rotate the images individually around their center point?
Also is there a way to keep the aspect ratio and scale the images with a larger view? Right now, the height and width are fixed.
*note I left out the code to load images as it not relevant to the question.
public final class PhotosView: UIView {
private let rightImageViewWidth: CGFloat = 98.0
private let rightImageViewHeight: CGFloat = 128.0
private let centerImageViewWidth: CGFloat = 128.0
private let centerImageViewHeight: CGFloat = 174.0
private let leftImageViewWidth: CGFloat = 98.0
private let leftImageViewHeight: CGFloat = 128.0
private let verticlePadding: CGFloat = 28.0
private var leftImageView: UIImageView
private var centerImageView: UIImageView
private var rightImageView: UIImageView
public init(frame: CGRect) {
self.leftImageView = UIImageView()
self.centerImageView = UIImageView()
self.rightImageView = UIImageView()
super.init(frame: frame)
self.addSubview(self.rightImageView)
self.addSubview(self.centerImageView)
self.addSubview(self.leftImageView)
}
required init(coder: NSCoder) { fatalError("Not implemented") }
public override func layoutSubviews() {
super.layoutSubviews()
let bounds = self.bounds
self.centerImageView.frame = CGRect(
x: bounds.midX - (centerImageViewWidth / 2.0), y: self.verticlePadding,
width: self.centerImageViewWidth,
height: self.centerImageViewHeight)
self.rightImageView.frame = CGRect(
x: self.centerImageView.frame.maxX - self.largePadding,
y: self.centerImageView.frame.minY + self.largePadding,
width: self.rightImageViewWidth,
height: self.rightImageViewHeight)
self.leftImageView.frame = CGRect(
x: self.centerImageView.frame.minX - self.leftImageViewWidth + self.largePadding,
y: self.centerImageView.frame.minY + self.largePadding, width: self.leftImageViewWidth,
height: self.leftImageViewHeight)
}
public override func sizeThatFits(_ size: CGSize) -> CGSize {
super.sizeThatFits(size)
let height = (2.0 * self.verticlePadding) + self.centerImageView.frame.height
return CGSize(width: size.width, height: height)
}

IntrinsicContentSize on a custom UIView streches the content

I would like to create a custom UIView which uses/offers a IntrinsicContentSize, since its height depends on its content (just like a label where the height depends on the text).
While I found a lot of information on how to work with IntrinsicContentSize offered by existing Views, I found just a few bits on how to use IntrinsicContentSize on a custom UIView:
#IBDesignable class MyIntrinsicView: UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.gray.cgColor)
context?.fill(CGRect(x: 0, y: 0, width: frame.width, height: 25))
height = 300
invalidateIntrinsicContentSize()
}
#IBInspectable var height: CGFloat = 50
override var intrinsicContentSize: CGSize {
return CGSize(width: super.intrinsicContentSize.width, height: height)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
invalidateIntrinsicContentSize()
}
}
The initial high is set to 50
The draw methods draws a gray rect with a size 25
height is changed to 300 and invalidateIntrinsicContentSize() is called
Placing a MyView instance in InterfaceBuilder works without any problem. The view does not need a height constraint
However, in IB the initial height of 50 is used. Why? IB draws the gray rect, thus the draw method is called. So why is the height not changed to 300?
What is also strange: When setting a background color it is drawn as well, also super.draw(...) is not called. Is this intended?
I would expect a view with height of 300 and a gray rect at the top with a height of 25. However, when running the project in simulator the result is different:
height of the view = 300 - OK
height of content (= gray rect) = 150 (half view height) - NOT OK
It seems that the content was stretched from its original height of 25 to keep its relative height to the view. Why is this?
Trying to change the view's height from inside draw() is probably a really bad idea.
First, as you've seen, changing the intrinsic content size does not trigger a redraw. Second, if it did, your code would go into an infinite recursion loop.
Take a look at this edit to your class:
#IBDesignable class MyIntrinsicView: UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.gray.cgColor)
context?.fill(CGRect(x: 0, y: 0, width: frame.width, height: 25))
// probably a really bad idea to do this inside draw()
//height = 300
//invalidateIntrinsicContentSize()
}
#IBInspectable var height: CGFloat = 50 {
didSet {
// call when height var is set
invalidateIntrinsicContentSize()
// we need to trigger draw()
setNeedsDisplay()
}
}
override var intrinsicContentSize: CGSize {
return CGSize(width: super.intrinsicContentSize.width, height: height)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
// not needed
//invalidateIntrinsicContentSize()
}
}
Now, when you change the intrinsic height in IB via the IBDesignable property, it will update in your Storyboard properly.
Here's a quick look at using it at run-time. Each tap (anywhere) will increase the height property by 50 (until we get over 300, when it will be reset to 50), which then invalidates the intrinsic content size and forces a call to draw():
class QuickTestVC: UIViewController {
#IBOutlet var testView: MyIntrinsicView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
var h: CGFloat = testView.intrinsicContentSize.height
h += 50
if h > 300 {
h = 50
}
testView.height = h
}
}

How can I increase the Tap Area for UIButton?

I use UIButton with auto layout. When images are small the tap area is also small. I could imagine several approaches to fix this:
increase the image size, i.e., place a transparent area around the image. This is not good because when you position the image you have to keep the extra transparent border in mind.
use CGRectInset and increase the size. This does not work well with auto layout because using auto layout it will fall back to the original image size.
Beside the two approaches above is there a better solution to increase the tap area of a UIButton?
You can simply adjust the content inset of the button to get your desired size. In code, it will look like this:
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
//Or if you specifically want to adjust around the image, instead use button.imageEdgeInsets
In interface builder, it will look like this:
Very easy. Create a custom UIButton class. Then override pointInside... method and change the value as you want.
#import "CustomButton.h"
#implementation CustomButton
-(BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect newArea = CGRectMake(self.bounds.origin.x - 10, self.bounds.origin.y - 10, self.bounds.size.width + 20, self.bounds.size.height + 20);
return CGRectContainsPoint(newArea, point);
}
#end
It will take more 10 points touch area for every side.
And Swift 5 version:
class CustomButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: -10, dy: -10).contains(point)
}
}
I confirm that Syed's solution works well even with autolayout. Here's the Swift 4.x version:
import UIKit
class BeepSmallButton: UIButton {
// MARK: - Functions
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let newArea = CGRect(
x: self.bounds.origin.x - 5.0,
y: self.bounds.origin.y - 5.0,
width: self.bounds.size.width + 10.0,
height: self.bounds.size.height + 20.0
)
return newArea.contains(point)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can set the button EdgeInsets in storyboard or via code. The size of button should be bigger in height and width than image set to button.
Note: After Xcode8, setting content inset is available in size inspecor
Or you can also use image view with tap gesture on it for action while taping on image view. Make sure to tick User Interaction Enabled for imageview on storyboard for gesture to work.
Make image view bigger than image to set on it and set image on it.
Now set the mode of image view image to center on storyboard/interface builder.
You can tap on image to do action.
Hope it will be helpful.
This should work
import UIKit
#IBDesignable
class GRCustomButton: UIButton {
#IBInspectable var margin:CGFloat = 20.0
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
//increase touch area for control in all directions by 20
let area = self.bounds.insetBy(dx: -margin, dy: -margin)
return area.contains(point)
}
}
Swift 5 version based on Syed's answer (negative values for a larger area):
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: -10, dy: -10).contains(point)
}
Alternatively:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.inset(by: UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5)).contains(point)
}
Some context about the edge insets answer.
When using auto layout combined with content edge insets you may need to change your constraints.
Say you have a 10x10 image and you want to make it 30x30 for a larger hit area:
Set your auto layout constraints to the desired larger area. If you
build right now this would stretch the image.
Using the content edge insets to shrink the space available to the
image so it matches the correct size. In this Example that would 10
10 10 10. Leaving the image with a 10x10 space to draw itself in.
Win.
Both solutions presented here do work ... under the right circumstances it is. But here are some gotchas you might run into.
First something not completely obvious:
tapping has to be WITHIN the button, touching the button bounds slightly does NOT work. If a button is very small, there is a good chance most of your finger will be outside of the button and the tap won't work.
Specific to the solutions above:
SOLUTION 1 #Travis:
Use contentEdgeInsets to increase the button size without increasing the icon/text size, similar to adding padding
button.contentEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
This one is straight forward, increasing the button size increases the tap area.
if you have set a height/width frame or constraint, obviously this doesn't do much, and will just distort or shift your icon/text around.
the button size will be bigger. This has to be considered when laying out other views. (offset other views as necessary)
SOLUTION 2 #Syed Sadrul Ullah Sahad:
Subclass UIButton and override point(inside point: CGPoint, with event: UIEvent?) -> Bool
class BigAreaButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: -20, dy: -20).contains(point)
}
}
This solution is great because it will allow you extend the tap area beyond the views bounds without changing the layout, but here are the catches:
a parent view needs to have a background, putting a button into an otherwise empty ViewController without a background won't work.
if the button is NESTED, all views up the view hierarchy need to either provide enough "space" or override point-in as well.
e.g.
---------
| |
|oooo |
|oXXo |
|oXXo |
|oooo | Button-X nested in View-o will NOT extend beyond View-o
---------
The way I'd approach this is to give the button some extra room around a small image using contentEdgeInsets (which act like a margin outside the button content), but also override the alignmentRect property with the same insets, which bring the rect that autolayout uses back in to the image. This ensures that autolayout calculates its constraints using the smaller image, rather than the full tappable extent of the button.
class HIGTargetButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setImage(_ image: UIImage?, for state: UIControl.State) {
super.setImage(image, for: state)
guard let image = image else { return }
let verticalMarginToAdd = max(0, (targetSize.height - image.size.height) / 2)
let horizontalMarginToAdd = max(0, (targetSize.width - image.size.width) / 2)
let insets = UIEdgeInsets(top: verticalMarginToAdd,
left: horizontalMarginToAdd,
bottom: verticalMarginToAdd,
right: horizontalMarginToAdd)
contentEdgeInsets = insets
}
override var alignmentRectInsets: UIEdgeInsets {
contentEdgeInsets
}
private let targetSize = CGSize(width: 44.0, height: 44.0)
}
The pink button has a bigger tappable target (shown pink here, but could be .clear) and a smaller image - its leading edge is aligned with the green view's leading edge based on the icon, not the whole button.
Subclass UIButton and add this function
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let verticalInset = CGFloat(10)
let horizontalInset = CGFloat(10)
let largerArea = CGRect(
x: self.bounds.origin.x - horizontalInset,
y: self.bounds.origin.y - verticalInset,
width: self.bounds.size.width + horizontalInset*2,
height: self.bounds.size.height + verticalInset*2
)
return largerArea.contains(point)
}
Swift 4 • Xcode 9
You can select programmatically as -
For Image -
button.imageEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
For Title -
button.titleEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
An alternative to subclassing would be extending UIControl, adding a touchAreaInsets property to it - by leveraging the objC runtime - and swizzling pointInside:withEvent.
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import "NSObject+Swizzling.h" // This is where the magic happens :)
#implementation UIControl (Extensions)
#dynamic touchAreaInsets;
static void * CHFLExtendedTouchAreaControlKey;
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSelector:#selector(pointInside:withEvent:) withSelector:#selector(chfl_pointInside:event:) classMethod:NO];
});
}
- (BOOL)chfl_pointInside:(CGPoint)point event:(UIEvent *)event
{
if(UIEdgeInsetsEqualToEdgeInsets(self.touchAreaInsets, UIEdgeInsetsZero)) {
return [self chfl_pointInside:point event:event];
}
CGRect relativeFrame = self.bounds;
CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaInsets);
return CGRectContainsPoint(hitFrame, point);
}
- (UIEdgeInsets)touchAreaInsets
{
NSValue *value = objc_getAssociatedObject(self, &CHFLExtendedTouchAreaControlKey);
if (value) {
UIEdgeInsets touchAreaInsets; [value getValue:&touchAreaInsets]; return touchAreaInsets;
}
else {
return UIEdgeInsetsZero;
}
}
- (void)setTouchAreaInsets:(UIEdgeInsets)touchAreaInsets
{
NSValue *value = [NSValue value:&touchAreaInsets withObjCType:#encode(UIEdgeInsets)];
objc_setAssociatedObject(self, &CHFLExtendedTouchAreaControlKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
Here is NSObject+Swizzling.h
https://gist.github.com/epacces/fb9b8e996115b3bfa735707810f41ec8
Here is a quite generic interface that allows you to reduce/increase the touch area of UIControls.
#import <UIKit/UIKit.h>
/**
* Extends or reduce the touch area of any UIControls
*
* Example (extends the button's touch area by 20 pt):
*
* UIButton *button = [[UIButton alloc] initWithFrame:CGRectFrame(0, 0, 20, 20)]
* button.touchAreaInsets = UIEdgeInsetsMake(-10.0f, -10.0f, -10.0f, -10.0f);
*/
#interface UIControl (Extensions)
#property (nonatomic, assign) UIEdgeInsets touchAreaInsets;
#end
If you're using Material's iOS library for your buttons, you can just use hitAreaInsets to increase the touch target size of the button.
example code from https://material.io/components/buttons/ios#using-buttons
let buttonVerticalInset =
min(0, -(kMinimumAccessibleButtonSize.height - button.bounds.height) / 2);
let buttonHorizontalInset =
min(0, -(kMinimumAccessibleButtonSize.width - button.bounds.width) / 2);
button.hitAreaInsets =
UIEdgeInsetsMake(buttonVerticalInset, buttonHorizontalInset,
buttonVerticalInset, buttonHorizontalInset);
Swift 5:
UIButton subclass implementation (for programmatically created buttons).
Tap area rect can be specified as either:
Absolute rect
Edge insets (e.g. 'top:left:bottom:right')
Note: changeTapAreaBy() is applied to button's initial bounds,
unless there are previous tap area adjustments, otherwise, to those.
Usage:
let image = UIImage(systemName: "figure.surfing")
let button = UIButton.systemButton(with: image, target: nil, action: nil)
button.changeTapAreaBy(insets: UIEdgeInsets(top: -5, left: -5, bottom: 5, right: 5)
Implementation (Swift 5):
import UIKit
class ConfigurableTapAreaButton : UIButton {
var tapRect = CGRect.zero
override init(frame: CGRect) {
super.init(frame: frame)
tapRect = bounds
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return tapRect.contains(point)
}
func setTapArea(rect: CGRect) {
tapRect = rect
}
func changeTapAreaBy(insets: UIEdgeInsets) {
let dx = insets.left
let dy = insets.top
let dw = insets.right - dx
let dh = insets.bottom - dy
tapRect = CGRect( x: tapRect.origin.x + dx,
y: tapRect.origin.y + dy,
width: tapRect.size.width + dw,
height: tapRect.size.height + dh)
}
}

trouble creating a scrollable UIView (swift + Interface Builder)

Please do not immediately discredit this a duplicate, I have been on stackoverflow for like 2 days now trying any and every way to do this, read a lot of blog articles (which might have been slightly dated) and still no luck.
Basically, I have a custom UIView which is meant to draw a lot of circles (it's just the starting point) and I cannot get the view to scroll down to the ones apart from the visible circles on initial load. I made the for loop for about 200 of them to be sure there is something to scroll to.
here is my view code:
import UIKit
class Draw2D: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
let colorSpace = CGColorSpaceCreateDeviceRGB();
let components: [CGFloat] = [0.0, 0.0, 1.0, 1.0];
let color = CGColorCreate(colorSpace, components);
CGContextSetStrokeColorWithColor(context, color);
var ctr = 0;
var ctr2 = 0;
for(var i:Int = 1; i<200; i++){
var ii: CGFloat=CGFloat(10+ctr);
var iii: CGFloat = CGFloat(30+ctr2);
CGContextStrokeEllipseInRect(context, CGRectMake(ii, iii, 33, 33));
if(ctr<200)
{
ctr+=160;
}else{
ctr=0;
ctr2+=50;
}
}
CGContextStrokePath(context);
}
}
In interface builder, I set the view class to Draw2D and the controller to my custom ViewController class. The view controller class is currently a standard one with no additional options added.
I tried dragging a Scroll View on the screen, I tried deleting the standard view, adding the scroll view and then putting my Draw2D view on top of that and it didn't work. I tried changing the size of the scroll view to make it smaller than my content (draw2d) view and nothing. I tried unchecking auto-layout and also changing simulated size to freeform as per certain tutorials I found online.
I also at one point tried creating a scroll view programmatically in the UIView and adding it as a subview which didn't help, maybe I did it wrong, I don't know but I'm really running out of options and sources to generate ideas from.
First, you can inherit from UIScrollView directly:
class Draw2D: UIScrollView {
Next, you need to set the content size of the UIScrollView to the height of the elements you inserted. For instance, let's say you have 4 circles:
let radius: CGFloat = 200
var contentHeight: CGFloat = 0
for i in 0...4 {
// initialize circle here
var circle = UIView()
// customize the view
circle.layer.cornerRadius = radius/2
circle.backgroundColor = UIColor.redColor()
// set the frame of the circle
circle.frame = CGRect(x: 0, y: CGFloat(i)*radius, width: radius, height: radius)
// add to scroll view
self.addSubview(circle)
// keep track of the height of the content
contentHeight += radius
}
At this point you have 4 circles one after the other, summing up to 800 px in height. Thus, you would set the content size of the scroll view as follows:
self.contentSize = CGSize(width: self.frame.width, height: contentHeight)
As far as contentHeight > device screen height then your view will scroll.
Full working code below:
import UIKit
import Foundation
class Draw2D: UIScrollView {
override init(frame: CGRect) {
super.init(frame: frame)
let radius: CGFloat = 200
var contentHeight: CGFloat = 0
for i in 0...4 {
// initialize circle here
var circle = UIView()
// customize the view
circle.layer.cornerRadius = radius/2
circle.backgroundColor = UIColor.redColor()
// set the frame of the circle
circle.frame = CGRect(x: 0, y: CGFloat(i)*radius, width: radius, height: radius)
// add to scroll view
self.addSubview(circle)
// keep track of the height of the content
contentHeight += radius
}
self.contentSize = CGSize(width: self.frame.width, height: contentHeight)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources