Making Buttons Circular in swift4 - xcode

I have few buttons which are added from Storyboard(not by code) . I want to make it circular in shape . How do I do it? I don't want a circular button added from code but from storyboard.Thanks

You can make use of IBDesginable to make UIButton circular even in storyboard.
Add this class RoundButton in your project.
#IBDesignable class RoundButton : UIButton{
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func prepareForInterfaceBuilder() {
sharedInit()
}
func sharedInit() {
refreshCorners(value: cornerRadius)
}
func refreshCorners(value: CGFloat) {
layer.cornerRadius = value
}
#IBInspectable var cornerRadius: CGFloat = 15 {
didSet {
refreshCorners(value: cornerRadius)
}
}
}
Follow below steps to make it designable in Storyboard.
Add UIButton in viewcontroller and set some background color.
Set Width and Height same. Here both are 100.
Now you can see one more property named Corner Radius we declared in RoundButton class. Set it to 50 as half of Width/Height. You can set according to your need.
Now you can see rounded corner button within storyboard. You do not need to run code to see changes. You can make reuse of class in any other project to make button circular.
More about IBDesignable.

Very simple and easy.
yourButton.layer.cornerRadius = yourButton.frame.width/2
this will surely going to work.
Eat sleep code repeat😇

let button = UIButton(type: .custom)
button.frame = CGRect(x: 160, y: 100, width: 50, height: 50)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.clipsToBounds = true
button.setImage(UIImage(named:"thumbsUp.png"), for: .normal)
button.addTarget(self, action: #selector(thumbsUpButtonPressed), for: .touchUpInside)

Related

Load multiple instances of a NSView from Nib

I was wondering if there was a method to create a view in a xib file and then connect its outlets to a class, so you can create multiple instances of that class and place them in the window.
I have some problems, so can you help me fix my code?
Here's what I did:
Firstly I created 2 files: CustomView.xib and CustomView.swift.
Then I designed the interface adding an NSImageView to the custom view. I set the file's owner to the class name and added an outlet from the NSImageView to the class.
Then I created the following function to load the interface from the nib:
func loadView() -> NSView {
var top = NSArray()
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: &top)
let view = top[0] as! NSView
return view
}
And set up the class to load the interface:
override init(frame: CGRect) {
super.init(frame: frame)
let view = loadView()
view.frame = bounds
addSubview(view)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
So I went on creating two variables of that class and placing it on the window:
var x = CustomView(frame: CGRect(x: 0, y: 0, width: 600, height: 105))
var y = CustomView(frame: CGRect(x: 0, y: 105, width: 600, height: 105))
And for some reasons this code gives me a strange error. It worked the first time, with one variable, but if I place more than one, it says it couldn't cast an NSWindow type in a NSView.
Could not cast value of type 'NSApplication' (0x7fffb5cf3ef0) to 'NSView' (0x7fffb5d04bb0).
I think that this error is given because sometimes the first top level object is the view, and sometimes it's the window. So I'm getting confused.
Obviously the error is thrown on this line:
let view = top[0] as! NSView
So what's the problem here?
(Please do not answer with cocoa touch code)
Use the filter function to get the NSView instance
func loadView() -> NSView {
var topLevelObjects = NSArray()
Bundle.main.loadNibNamed("CustomView", owner: self, topLevelObjects: &topLevelObjects)
let views = (topLevelObjects as Array).filter { $0 is NSView }
return views[0] as! NSView
}

Custom TableViewCell with Subview whose height changes

I'm using a TableView and have a custom TableViewCell that I've added a subview to.
The problem is that I need the subview's height to change sometimes and therefore, the table's contentView would have to be updated as well as the row's height.
The subview of the custom TableViewCell is represented by the yellow background.
These images show what's currently happening in my simulator.
On Load
After the event that causes the subview's height to increase
What's the best approach to take with something like this?
Should I use constraints? And if so, what kind of constraints should I use? Would I have to then reload the tableview too every time the subview's size changes?
Here is the code I'm currently using for my custom TableViewCell:
import UIKit
class CustomCell: UITableViewCell {
var newView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.newView = UIView(frame: self.frame)
self.newView.backgroundColor = .yellowColor()
self.addSubview(newView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.newView.frame.size.width = self.frame.size.width // because self.frame.width is different than it was in the init method
}
func somethingHappenedThatMySubviewHasToIncreaseInHeight() {
self.newView.frame.size.height = self.frame.size.height + 40
}
}
The best approach is to use Auto Layout and self-sizing cells. Setup constraints in storyboard for your custom cell.
You will not need to reload the tableView. Each cell will automatically adjust its height, based on how much vertical space its subview takes.
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.

How to implement NSTrackingArea's mouseEntered/Exited with animation?

I want to implement a feature that when an user hovers over the specific area, the new view appears with drawer-like animation. And also, when the user leaves the specific area, the drawer should go away with animation. This is exactly what you see when you hover over the bottom of the screen in OS X, where the Dock appears and disappears with animation.
However, if I implement the feature with animation, it does not work properly when you re-enter the specific area before the animation in the mouseExited: is completed. Here's my code:
let trackingArea = NSTrackingArea(rect: CGRectMake(0, 0, 120, 300), options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.MouseEnteredAndExited, owner: self, userInfo: nil)
underView.addTrackingArea(trackingArea) // underView is the dummy view just to respond to the mouse tracking, since the drawerView's frame is changed during the animation; not sure if this is the clean way...
override func mouseEntered(theEvent: NSEvent) {
let frameAfterVisible = CGRectMake(0, 0, 120, 300)
NSAnimationContext.runAnimationGroup({
(context: NSAnimationContext!) in
context.duration = 0.6
self.drawerView.animator().frame = frameAfterVisible
}, completionHandler: { () -> Void in
})
}
override func mouseExited(theEvent: NSEvent) {
let frameAfterInvisible = CGRectMake(-120, 0, 120, 300)
NSAnimationContext.runAnimationGroup({
(context: NSAnimationContext!) in
context.duration = 0.6
self.drawerView.animator().frame = frameAfterInvisible
}, completionHandler: { () -> Void in
})
}
// drawerView's frame upon launch is (-120, 0, 120, 300), since it is not visible at first
In this code, I animate the drawerView by altering its x position. However, as I stated, when you enter the tracking area and then leave the tracking area, the drawer works correctly. But that is not the case if you re-enter the tracking area before the leave-off animation is fully completed.
Of course if I set the animation duration shorter, such as 0.1, this would rarely occur. But I want to move the view with animation.
What I want to do is make the drawerView start to appear again even if the view has not completed disappearing. Is there any practice to do it?
I have a solution that is very similar to your code. What I do different is is that I install the NSTrackingArea not on the view that contains the drawer view, but on the drawer view itself.
This obviously means that the drawer needs to 'stick out' a little bit. In my case the drawer is a small bit visible when it is down because I put an image view in it. If you don't want that then I suggest you just leave the visible area of the drawer empty and translucent.
Here is my implementation:
private enum DrawerPosition {
case Up, Down
}
private let DrawerHeightWhenDown: CGFloat = 16
private let DrawerAnimationDuration: NSTimeInterval = 0.75
class ViewController: NSViewController {
#IBOutlet weak var drawerView: NSImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Remove the auto-constraints for the image view otherwise we are not able to change its position
view.removeConstraints(view.constraints)
drawerView.frame = frameForDrawerAtPosition(.Down)
let trackingArea = NSTrackingArea(rect: drawerView.bounds,
options: NSTrackingAreaOptions.ActiveInKeyWindow|NSTrackingAreaOptions.MouseEnteredAndExited,
owner: self, userInfo: nil)
drawerView.addTrackingArea(trackingArea)
}
private func frameForDrawerAtPosition(position: DrawerPosition) -> NSRect {
var frame = drawerView.frame
switch position {
case .Up:
frame.origin.y = 0
break
case .Down:
frame.origin.y = (-frame.size.height) + DrawerHeightWhenDown
break
}
return frame
}
override func mouseEntered(event: NSEvent) {
NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext!) in
context.duration = DrawerAnimationDuration
self.drawerView.animator().frame = self.frameForDrawerAtPosition(.Up)
}, completionHandler: nil)
}
override func mouseExited(theEvent: NSEvent) {
NSAnimationContext.runAnimationGroup({ (context: NSAnimationContext!) in
context.duration = DrawerAnimationDuration
self.drawerView.animator().frame = self.frameForDrawerAtPosition(.Down)
}, completionHandler: nil)
}
}
Full project at https://github.com/st3fan/StackOverflow-28777670-TrackingArea
Let me know if this was useful. Happy to make changes.
Starting Swift 3. You need to do it like this:
let trackingArea = NSTrackingArea(rect: CGRectMake(0, 0, 120, 300), options: [NSTrackingAreaOptions.ActiveAlways ,NSTrackingAreaOptions.MouseEnteredAndExited], owner: self, userInfo: nil)
view.addTrackingArea(trackingArea)
Credits #marc above!

How to make a UIImageView tappable and cause it to do something? (Swift)

(I just started using Swift a few days ago and am relatively new to programming, so please bear with me.) I am trying to make random blocks appear on the screen, and the user must tap them to make them disappear. I have been able to create the blocks, but I have no idea how to actually make them tappable. Can someone please help me? This is my code so far:
func createBlock(){
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -50, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
})
}
Edit: This is the new code I am trying:
func createBlock(){
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -50, width: size, height: size)
self.view.addSubview(imageView)
imageView.userInteractionEnabled = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped"))
imageView.addGestureRecognizer(tapRecognizer)
func imageTapped(gestureRecognizer: UITapGestureRecognizer) {
let tappedImageView = gestureRecognizer.view!
tappedImageView.removeFromSuperview()
score += 10
}
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
})
}
Once you've create the view, you need to set it's userInteractionEnabled property to true. Then you need to attach a gesture to it.
imageView.userInteractionEnabled = true
//now you need a tap gesture recognizer
//note that target and action point to what happens when the action is recognized.
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
//Add the recognizer to your view.
imageView.addGestureRecognizer(tapRecognizer)
Now you still need the function, in this case imageTapped:, which is where the action happens when the gesture is recognized. The gesture that was recognized will be sent as an argument, and you can find out which imageView was tapped from they gestures view property.
func imageTapped(gestureRecognizer: UITapGestureRecognizer) {
//tappedImageView will be the image view that was tapped.
//dismiss it, animate it off screen, whatever.
let tappedImageView = gestureRecognizer.view!
}
In swift 3.0:
imvEditName.isUserInteractionEnabled = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
imvEditName.addGestureRecognizer(tapRecognizer)
And the target method:
func imageTapped(sender: UIImageView) {
print("image tapped")
}
My previous answer is still accurate for adding a tap recognizer to a UIImageView, however it is not sufficient for the purposes of a view that is animating. If you are actually creating a game, you'll likely want to look into SpriteKit, as it is literally tailor made for this. However, this can be accomplished with normal UIKit. I've tested two approaches, which both work.
Both approaches worked far better on an actual device than in the simulator
Approach 1, Simpler yet slightly less precise up to a tenth of second slower in my tests.
Instead of adding a tap gesture to the UIImageView, add the gesture to the superview. Keep a reference to your blocks in an array property, and check if any blocks are intercepted by the tap.
class ViewController: UIViewController {
let size = CGFloat(40)
let xPosition = CGFloat(14)
let options = UIViewAnimationOptions.Autoreverse
var blocks = [UIImageView]()
override func viewDidLoad() {
super.viewDidLoad()
// Adding recognizer to the whole view.
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("screenTapped:"))
view.addGestureRecognizer(tapRecognizer)
blocks.append(createBlock())
}
//changed to return the created block so it can be stored in an array.
func createBlock() -> UIImageView {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -40, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(2, delay: 0.0, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.blocks.append(self.createBlock())
})
return imageView
}
func screenTapped(gestureRecognizer: UITapGestureRecognizer) {
let tapLocation = gestureRecognizer.locationInView(view)
//This is to keep track of the blocks to remove from the array.
//If you remove them as you iterate the array, and you have more than
//one block to remove, you may end up removing the wrong block
//or with an index out of bounds.
var removeBlocks = [Int]()
for (index, block) in enumerate(blocks) {
//this is the frame of the view as we see it on screen.
//unfortunately block.frame is not where we see it making a recognizer
//on the block itself not work.
if block.layer.presentationLayer().frame.contains(tapLocation) {
//Then this block was clicked.
block.removeFromSuperview()
removeBlocks.append(index)
}
}
//get the indexes ro remove backwards so we are removing from
//back to front.
for index in removeBlocks.reverse() {
blocks.removeAtIndex(index)
}
}
}
Approach 2, Best Performance outside of SpriteKit
In approach two, you subclass UIView, and set your main view to it instead of the UIView. You only need to override a single method. I'm storing the block array in the view for convenience.
First the TouchView.
import UIKit
class TouchView: UIView {
var blocks = [UIImageView]()
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// ignoring multiple touches for now
if touches.count == 1 {
if let touch = touches.first as? UITouch {
let touchLocation = touch.locationInView(self)
//This is to keep track of the blocks to remove from the array.
//If you remove them as you iterate the array, and you have more than
//one block to remove, you may end up removing the wrong block
//or with an index out of bounds.
var removeBlocks = [Int]()
for (index, block) in enumerate(blocks) {
//this is the frame of the view as we see it on screen.
//unfortunately block.frame is not where we see it making a recognizer
//on the block itself not work.
if block.layer.presentationLayer().frame.contains(touchLocation) {
//Then this block was clicked.
block.removeFromSuperview()
removeBlocks.append(index)
}
}
//get the indexes ro remove backwards so we are removing from
//back to front.
for index in removeBlocks.reverse() {
blocks.removeAtIndex(index)
}
}
}
}
}
Now the ViewController would look like:
import UIKit
class ViewController: UIViewController {
let size = CGFloat(40)
let xPosition = CGFloat(14)
let options = UIViewAnimationOptions.Autoreverse
var blocks = [UIImageView]()
// computed get only property for conveniece of accessing block array
var touchView:TouchView {
get {
return self.view as! TouchView
}
}
override func viewDidLoad() {
super.viewDidLoad()
touchView.blocks.append(createBlock())
// Do any additional setup after loading the view, typically from a nib.
}
func createBlock() -> UIImageView {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -40, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(2, delay: 0.0, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.touchView.blocks.append(self.createBlock())
})
return imageView
}
}
In my tests, this seems to work pretty good, even with quickly animated "blocks". Again, if you are planning on making a full fledged game, you should really look at SpriteKit. There is a really simple tutorial here that should get you started. If you can remove the accepted answer from my previous answer, that would help others not be lead down the wrong path, unless they only are looking to add a gesture to a non-moving UIImageView.
Instead using a UIImageView, you could try use a Custome UIButton and replace the default button image with yours. Hence, you do not need to add UITapGestureRecognizer for each UIImageView. #JeremyPope 's approach is actually more efficient compare to this approach.
Handling UIButton is pretty simple, its subclass of UIControl so there are properties like hide and enabled and you can manipulate them in the way you want.
What you need to do is to create UIButton programmaticly, just like you they way you creating UIImageView. The way to create them is similar to UIImageView, read more on create UIButton programmaticly in Swift.
The image below is how a custom UIButton looks like.
Leave comments if you need more info.
By Default, image view user interaction is not enabled, so be sure to enable it by setting it to true.
let myImageView = UIImageView()
myImageView.isUserInteractionEnabled = true

Swift NSStackView : Learn By Doing. What am I doing wrong?

How do I get my NSStackView to lay out my view one after the other ? My blue box is drawing on top of my red box.
import Cocoa
class TestView : NSView{
override init() {
super.init(frame: NSRect(x: 0,y: 0,width: 100,height: 100))
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.redColor().setFill()
NSBezierPath.fillRect(self.bounds)
}
}
class TestView2 : NSView{
override init() {
super.init(frame: NSRect(x: 0,y: 0,width: 100,height: 100))
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.blueColor().setFill()
NSBezierPath.fillRect(self.bounds)
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var searchFacetItems: SearchFacetSelectorView!
#IBOutlet weak var searchFacetHeader: SearchFacetSelectorHeader!
var content : NSStackView!
let testView = TestView()
let testView2 = TestView2()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
content = NSStackView(frame: window.contentView.bounds)
content.orientation = NSUserInterfaceLayoutOrientation.Vertical
content.alignment = NSLayoutAttribute.CenterX
content.addView(testView, inGravity: NSStackViewGravity.Center)
content.addView(testView2, inGravity: NSStackViewGravity.Center)
window.contentView = content!
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
learn by reading
It's not appkit, but maybe i'll get some clues: HOW TO USE UIVIEWS WITH AUTO LAYOUT PROGRAMMATICALLY
Auto Layout Guide
You should be able to do this by adjusting the NSRect coordinates. Currently you've got both NSRects using the same exact coordinates:
NSRect(x: 0, y: 0, width: 100, height: 100)
If you want the NSView horizontally (on the right) located next of the other:
NSRect(0, 0, 100, 100) // TestView1
NSRect(100, 0, 100, 100) // TestView2
vertically below:
NSRect(0, 0, 100, 100) // TestView1
NSRect(0, -100, 100, 100) // TestView2
The views don't have any constraints that describe their preferred sizes, such as intrinsicContentSizes or explicit constraints. And NSStackView only adds constraints that positions its stacked views relative to each other, not ones to size individual views.
Without them, their size in the stacking axis becomes ambiguous and will typically give all of the sizing to a single view. In your example, I'd guess that the blue box is not drawing on top of the red box, but just that the red box has a 0 height (and is stacked after the blue view).
Adding NSButtons to a stack view don't have this problem, as they do have a defined intrinsicContentSize.
Depending on what your views are -- do they have intrinsic sizes, or will they be defined by constraints to internal subviews -- you'll either want to override intrinsicContentSize or add those constraints that end up defining their heights.
Edit: Oh, and make sure translatesAutoresizingMaskIntoConstraints is set to false on the views you're adding to the stack view (it doesn't look like you are from the sample code). If not, you'll quickly run into trouble where the autoresizing constraints are trying to position the view and conflicting with constraints that the stack view adds.

Resources