How to manipulate UIButtons via tag - ios8

I'm trying to create a simple AI for a tic tac toe game that already allows two players to select from nine invisible buttons. Using sender.tag I am able to set the background of each button to display an 'X' or 'O.'
Now I want to create an AI but I don't know how to set each button background based on the AI selection. I tried to do this through the tag parameter, but it doesn't work because there is no sender.
What's the best way to go about manipulating the background (or any parameter of a UIButton with no name)?
Thanks,
Matt

Identifying UI elements by their tags is not the best practice. If you design your UI with Interface Builder then you can assign those UIButtons to an array of UIButton outlets which then allows you access to necessary object which should be manipulated.
1) Declare an array of outlets:
#IBOutlet var buttons: [UIButton]!
2) Connect in Interface Builder all UIButtons to the buttons outlet in predefined order
3) Use an extension for UIButton to change its color:
extension UIButton {
private func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func setBackgroundColor(color: UIColor, forUIControlState state: UIControlState) {
self.setBackgroundImage(imageWithColor(color), forState: state)
}
}
4) Then refer to the selected UIButton from the outlet array when AI computes the move:
buttons[int_of_AI_selected_button].setBackgroundColor(thecolor, forthecontrolstate)

Related

Animating constraints with layer-backed NSView

I'm attempting to implement an animation that shows/hides a view in a horizontal arrangement. I'd like this to happen with slide, and with no opacity changes. I'm using auto-layout everywhere.
Critically, the total width of the containing view changes with the window. So, constant-based animations are not possible (or so I believe, but happy to be proved wrong).
|- viewA -|- viewB -|
My first attempt was to use NSStackView, and animate the isHidden property of an arranged subview. Despite seeming like it might do the trick, I was not able to pull off anything close to what I was after.
My second attempt was to apply two constraints, one to force viewB to be zero width, and a second to ensure the widths are equal. On animation I change the priorities of these constraints from defaultHigh <-> defaultLow.
This results in the correct layout in both cases, but the animation is not working out.
With wantsLayer = true on the containing view, no animation occurs whatsoever. The views just jump to their final states. Without wantsLayer, the views do animate. However, when collapsing, viewA does a nice slide, but viewB instantly disappears. As an experiment, I changed the zero width to a fixed 10.0, and with that, the animation works right in both directions. However, I want the view totally hidden.
So, a few questions:
Is it possible to animate layouts like this with layer-backed views?
Are there other techniques possible for achieving the same effect?
Any ideas on how to achieve these nicely with NSStackView?
class LayoutAnimationViewController: NSViewController {
let containerView: NSView
let view1: ColorView
let view2: ColorView
let widthEqualContraint: NSLayoutConstraint
let widthZeroConstraint: NSLayoutConstraint
init() {
self.containerView = NSView()
self.view1 = ColorView(color: NSColor.red)
self.view2 = ColorView(color: NSColor.blue)
self.widthEqualContraint = view2.widthAnchor.constraint(equalTo: view1.widthAnchor)
widthEqualContraint.priority = .defaultLow
self.widthZeroConstraint = view2.widthAnchor.constraint(equalToConstant: 0.0)
widthZeroConstraint.priority = .defaultHigh
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
self.view = containerView
// view.wantsLayer = true
view.addSubview(view1)
view.addSubview(view2)
view.subviewsUseAutoLayout = true
NSLayoutConstraint.activate([
view1.topAnchor.constraint(equalTo: view.topAnchor),
view1.bottomAnchor.constraint(equalTo: view.bottomAnchor),
view1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// view1.trailingAnchor.constraint(equalTo: view2.leadingAnchor),
view2.topAnchor.constraint(equalTo: view.topAnchor),
view2.bottomAnchor.constraint(equalTo: view.bottomAnchor),
view2.leadingAnchor.constraint(equalTo: view1.trailingAnchor),
view2.trailingAnchor.constraint(equalTo: view.trailingAnchor),
widthEqualContraint,
widthZeroConstraint,
])
}
func runAnimation() {
view.layoutSubtreeIfNeeded()
self.widthEqualContraint.toggleDefaultPriority()
self.widthZeroConstraint.toggleDefaultPriority()
// self.leadingConstraint.toggleDefaultPriority()
NSAnimationContext.runAnimationGroup({ (context) in
context.allowsImplicitAnimation = true
context.duration = 3.0
self.view.layoutSubtreeIfNeeded()
}) {
Swift.print("animation complete")
}
}
}
extension LayoutAnimationViewController {
#IBAction func runTest1(_ sender: Any?) {
self.runAnimation()
}
}
Also, some potentially relevant, but so far unhelpful, related questions:
Animating Auto Layout changes concurrently with NSPopover contentSize change
Animating Auto Layout constraints with NSView.layoutSubtreeIfNeeded() not working on macOS High Sierra
Hide view item of NSStackView with animation

iOS vertical center dynamic height stack layout in autolayout

I am trying to create a vertically and horizontally centered stack view with dynamic height content. Something like this:
I and I am almost there, but the problem is that when I add a button which height should be dynamic (scale with label) I get this: (Note the overlapping buttons).
Here's what I currently have:
Without that button with the long label, the view looks good. Now I've read about the concepts of how the auto layout works, that It needs to know the height of the content to center it. But about the cases when the content height is unknown?
Setting the titleLabel of a UIButton to multi-line (changing Line Break to Word Wrap) doesn't change the intrinsicContentSize of the button, so you have to do it yourself.
Here is one approach. Subclass UIButton like this:
class CheckboxButton: UIButton {
override var intrinsicContentSize: CGSize {
return titleLabel?.intrinsicContentSize ?? CGSize.zero
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = titleLabel?.frame.size.width ?? 0
super.layoutSubviews()
}
}
You can confirm the sizing is being done correctly by giving your button a background color (during development) to clearly see its bounds / frame.
After that, the spacing should be handled correctly by auto-layout.

How to create AppleTV buttons?

At first glance they look like regular UIButtons however they got a label below it. Also the background of the button seems to be a blurred effect.
So my thoughts are that they are put in a CollectionView (Horizontal). With each cell containing a UIButton and a UILabel. Although that may work the UIButton doesn't seem to get the move effect for free.
Is that custom behavior? And if so, how are you able to create such an effect?
I bet it is not an UICollectionView but a horizontal UIStackView of custom views in which there is a UIButton and UILabel vertically aligned.
Here you have an example, using stackViews:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.alignment = .center
stackView.spacing = 30
view.addSubview(stackView)
["One", "Two", "Three", "Caramba"].forEach {
let buttonStackView = UIStackView()
buttonStackView.axis = .vertical
buttonStackView.distribution = .fillProportionally
buttonStackView.alignment = .center
buttonStackView.spacing = 15
let button = UIButton(type: .system)
button.setTitle($0, for: .normal)
buttonStackView.addArrangedSubview(button)
let label = UILabel()
label.text = $0
label.font = UIFont.systemFont(ofSize: 20)
buttonStackView.addArrangedSubview(label)
stackView.addArrangedSubview(buttonStackView)
}
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Having a custom view instead of a vertical uistackview for each button would allow to customize its layout when focused, including Parallax effect.
For adding parallax effect to each button in the stack, take a look to How to get Parallax Effect on UIButton in tvOS?

Swift, Xcode: Changing UIButton Background and Text on Click

I want to change the background and text of the button on click, I tried several SO solutions but they haven't worked, you can see what I tried in my project:
https://github.com/jzhang172/modalTest
I tried debugging it by putting a simple print statement and it looks like it doesn't ever go to it.
UIButton's have a method for setting the title color. So if you had a UIButton IBOutlet named myBtn:
myBtn.setTitleColor(UIColor.blackColor(), forState: .Highlighted)
and to change the text of the button on touch:
myBtn.setTitle("This button was touched", forState: .Highlighted)
As far as setting the background color, you could add an extension for your UIButton which allows you to do this:
extension UIButton {
private func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func setBackgroundColor(color: UIColor, forUIControlState state: UIControlState) {
self.setBackgroundImage(imageWithColor(color), forState: state)
}
}
Then you could do:
myBtn.setBackgroundColor(UIColor.grayColor(), forUIControlState: .Highlighted)
Hope this helps!
SOLUTION:
1) Create an IBAction from your UIButton and also an IBOutlet called button.
EDIT: As per your request (How to trigger the even when the button is TOUCHED, not RELEASED?):
2) Do this:
#IBAction func changes (sender: UIButton) {
button.backgroundColor = UIColor.blueColor()
button.setTitle("Button Title", forState: UIControlState.Normal)
}
Your UIButton #IBOutlet closePop is hooked up to a single #IBAction of the exact same name (closePop()). That closePop() IBAction ONLY dismisses the helpViewController - it doesn't do anything about the text or button color.
Your other #IBAction function like, in which you try to set the color & print "Is this even working?", is not hooked up to the button, and is never called.

UiScrollview with nested image looks weird

I have a UIScrollView inside a UIViewController (subclassed by ImageViewController). The ViewController itself is part of a NavigationController's stack. Now, apart from having a navigation bar, I want the ScrollView to take all of the available room on the screen. The UIImageView inside the scrollview should then fill the available room of the scroll view. You can see the current state at the bottom of this posting.
class ImageViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var imageView: UIImageView?
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
if let image = image {
imageView = UIImageView(image: image)
if let imageView = imageView {
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: image.size)
scrollView.addSubview(imageView)
scrollView.contentSize = image.size
let scaleHeight = scrollView.frame.size.height / scrollView.contentSize.height
let scaleWidth = scrollView.frame.size.width / scrollView.contentSize.width
let minimumScale:CGFloat = min(scaleHeight, scaleWidth)
let maximumScale:CGFloat = max(scaleHeight, scaleWidth)
scrollView.minimumZoomScale = minimumScale
scrollView.maximumZoomScale = maximumScale
scrollView.zoomScale = maximumScale
}
}
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
The code leaves me with unnecessary borders (left, right, top). How do I get rid of them?
EDIT: With #Bxtr's suggestion and another stackoverflow thread I was able to remove the borders left and right to the scroll view. After some more digging I found out that by deactivating Adjust Scroll View Insets, the image inside the scroll view can be correctly vertically positioned. Still, I do not get the reason for the vertical misplacement in the first place...
Have you checked the margin/padding values, because it kinda looks so (same size on left and right border). If it is not the case, could you please also post your xml file of the activity so we can have every part of the puzzle to help you ?
scrollView.contentSize = image.size;
you have to tweek this line. You are explicitly setting scroll view content size to the image size. You have to set content size to fit the Width of Screen.
You can use a UIView in UIScrollView, and that UIView contains UIImage.
You need to set constraints properly.
After some more digging I found out that by deactivating Adjust Scroll
View Insets, the image inside the scroll view can be correctly
vertically positioned. Still, I do not get the reason for the vertical
misplacement in the first place...
The reason is that the view controller's automaticallyAdjustsScrollViewInsets property is by default YES, the following is from apple documentation:
automaticallyAdjustsScrollViewInsets
A Boolean value that indicates
whether the view controller should automatically adjust its scroll
view insets.
Default value is YES, which allows the view controller to adjust its
scroll view insets in response to the screen areas consumed by the
status bar, navigation bar, and toolbar or tab bar. Set to NO if you
want to manage scroll view inset adjustments yourself, such as when
there is more than one scroll view in the view hierarchy.
Besides setting automaticallyAdjustsScrollViewInsets = No, you can pin the scrollView to the topLayoutGuide (instead of to the top of the viewController's view) when using autoLayout.

Resources