Programming a UIButton is not working in Swift 4 Xcode 9 Beta 6, Cannot add target - uibutton

In the past I had no problem creating a UIButton programmatically, but ever since I've been using Xcode 9 and Swift 4, I cannot find a way to make this error go away.
//Adding target to UIButton
func PositionStartButton(xOffset: Float){
StartButton.frame = CGRect(x: Int(0 - 40 + xOffset), y: 0, width: 80, height: 80)
StartButton.setImage(#imageLiteral(resourceName: "Logo_Final_WHITE_Face"), for: .normal)
StartButton.addTarget(self, action: "pressButton:", for: .touchUpInside)
ScrollView.addSubview(StartButton)
}
//The target function
func pressButton(_ sender: UIButton){
print("\(sender)")
}
Error Message: 'NSInvalidArgumentException', reason: '-[Playing.MainMenuViewController pressButton:]: unrecognized selector sent to instance 0x10440a6b0'

Two points.
First, since Swift 2.2 (bundled with Xcode 7.3, released more than a year ago), recommended selector notation is #selector(...). With using the notation, you may get more useful diagnostic messages, than using other notation.
(You should not ignore any warnings displayed with recommended settings.)
Seconde, in Swift 4, you need to explicitly annotate methods invoked through selector with #objc. (In very limited cases, Swift implicitly applies the notation, but not many.)
So, your code shown should be:
//Adding target to UIButton
func PositionStartButton(xOffset: Float){
StartButton.frame = CGRect(x: Int(0 - 40 + xOffset), y: 0, width: 80, height: 80)
StartButton.setImage(#imageLiteral(resourceName: "Logo_Final_WHITE_Face"), for: .normal)
StartButton.addTarget(self, action: #selector(self.pressButton(_:)), for: .touchUpInside) //<- use `#selector(...)`
ScrollView.addSubview(StartButton)
}
//The target function
#objc func pressButton(_ sender: UIButton){ //<- needs `#objc`
print("\(sender)")
}
This is not critical, but you should better follow a simple coding rule of Swift -- only type names are capitalized.
Better rename your PositionStartButton, StartButton and ScrollView, if you think you may have another chance to show your code publicly.

// Add Target
buttonName.addTarget(self, action: #selector(ViewController.handleButton(_:)), for: .touchUpInside)
// Handle Action
#objc func handleButton(_ sender: AnyObject) {
}

Related

SwiftUI submitLabel not working on TextEditor

I want to use .submitLabel to change the "return" key to something else (namely, .done) but it does not appear to work with the TextEditor input form. Is it supposed to?
Sample code:
struct ContentView: View {
#State var text: String = ""
var body: some View {
Form {
TextEditor(text: $text)
.submitLabel(.search)
}
}
}
Upcoming Support
Support was added in Xcode 13.2 beta 3, which is not yet available.
Temporary Fix
Ok, so based on my attempts, .submitLabel(...) doesn't function with TextEditor. There is a possible solution where you add a Text(...) as a subview to the TextEditor(...) but that is extremely jenk and I wouldn't recommend that. There is however a solution I found for iOS 13 that seems to function the exact same way that the TextEditor appears to work and that's to use UIViewRepresentable. Thankfully it's very easy to implement. Create this struct.
struct TextView: UIViewRepresentable {
typealias UIViewType = UITextView
var configuration = { (view: UIViewType) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType {
UIViewType()
}
func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) {
configuration(uiView)
}
}
Then use that view as you would use a TextView() where you assign its properties. This is effectively pulling over the UIKit version of a textView, so its properties will be the same. You can get fancy with it at this point and mix SwiftUI and UIKit properties and modifiers.
TextView { view in
view.text = sampleText
view.returnKeyType = .done
}
Although I have no evidence, I would think that since Apple defines a TextEditor as:
"A text editor view allows you to display and edit multiline, scrollable text in your app’s user interface",
The "Return" key is needed for line feeds, and may therefore not be able to be affected by .submitLabel
https://developer.apple.com/documentation/swiftui/texteditor

Not able to debug notificaton content extension in ios 10 notification

print statement does not work inside notification content extension, although I am able to modify the Label text and other fields, below is my code
class NotificationViewController: UIViewController, UNNotificationContentExtension {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
print("inside viewDidLoad of notificationViewController")
}
func didReceive(_ notification: UNNotification) {
self.label?.text = notification.request.content.body
print("inside didReceive of notificationViewController")
}
}
The following steps pointed here worked for me:
After running the app that contains the extension,
Set your breakpoint in the extension
Select Debug / Attach to Process by PID or name
Enter the name of the extension target
Trigger the push notification
Breakpoints in extensions take forever to trigger. BE PATIENT, eventually your breakpoint will be hit
You will have to do this every time you restart your app via Xcode.

Xcode 8 Beta 4 CGColor.components unavailable

Whilst the code below worked previously, it has stopped working in Xcode 8 Beta 4, presumably because the components return was a very un-Swift-y C-array of floats, and has been removed. The error is bald - "'components' is unavailable" - and I can't find what has replaced it, if anything. Does anyone know how to produce the same functionality?
public var cgColour: CGColor {
get {
return CGColor(red: self.colourRed, green: self.colourGreen, blue: self.colourBlue, alpha: self.colourAlpha)
}
set {
let comps = newValue.components // No longer available
self.colourRed = (comps?[0])!
self.colourGreen = (comps?[1])!
self.colourBlue = (comps?[2])!
self.colourAlpha = (comps?[3])!
}
}
Update
#Hamish's answer works, but my original intent was not to use UIColor nor NSColor so that my code works in both iOS & MacOS. What I've ended up doing is this...
#if os(iOS)
import UIKit
#elseif os(OSX)
import Cocoa
#endif
extension CGColor {
var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
#if os(iOS)
UIColor(cgColor: self).getRed(&r, green: &g, blue: &b, alpha: &a)
#elseif os(OSX)
NSColor(cgColor: self)?.getRed(&r, green: &g, blue: &b, alpha: &a)
#endif
return (r, g, b, a)
}
}
// Playground code to test...
#if os(iOS)
let rgba = UIColor.brown.cgColor.components //(0.6, 0.4, 0.2, 1.0)
#elseif os(OSX)
let rgba = NSColor.brown.cgColor.components //(0.6, 0.4, 0.2, 1.0)
#endif
... this is such a kludge - has anyone got a better answer?
This looks like a transitional beta issue.
The Swift repo on Github includes an extensive SDK overlay for CoreGraphics, including a new version of CGColor.components whose return type is a Swift array instead of an UnsafePointer. Part of how they make that SDK overlay work is API notes, which map some of the underlying C calls to double-underscore'd Swift methods so that the overlay can wrap them in a more-Swifty interface.
It looks like the beta 4 and beta 5 compilers picked up the API notes change, but not the overlay that includes the new version of components. Presumably a future beta (or the final Swift 3.0 / Xcode 8.0 release) will include everything that's now on github.
#Hamish's answer works, but my original intent was not to use UIColor or NSColor so that my code works in both iOS & MacOS. #LeoDabus suggested using SKColor, but that's just a type alias to either NSColor or UIColor, and doesn't have a direct init from CGColor anyway, however, Leo's suggestion prompted me to refine my kludge using CIColor instead:
import CoreImage
extension CGColor {
var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
let ciColor = CIColor(cgColor: self)
return (ciColor.red, ciColor.green, ciColor.blue, ciColor.alpha)
}
}
I'm also a little perplexed as to why they have removed the components property from CGColor, as there doesn't seem to be any kind of obvious replacement method/property. It looks like they're trying to get people to use the higher level UIColor or NSColor classes instead. (As #rickster has discovered, this looks more like a simple transitional issue).
One solution, as you're working with RGB colors, would be to simply wrap your CGColor in a UIColor/NSColor, and then use the getRed(_:green:blue:alpha:) method to get out the components instead.
public var cgColour : CGColor {
get {
return CGColor(red: colourRed, green: colourGreen, blue: colourBlue, alpha: colourAlpha)
}
set {
NSColor(cgColor: newValue)?.getRed(&colourRed, green: &colourGreen, blue: &colourBlue, alpha: &colourAlpha)
}
}
Perhaps not the most ideal solution – would certainly be interested to know if anyone else has a better one, or knows more about this change. Depending on the usage of this property, you may also want to consider simply making it of type UIColor/NSColor to prevent the needless wrapping that this solution requires.
I may be mistaking something, but you can find this in the imported header of CGColor.
/* Return the color components (including alpha) associated with `color'. */
#available(OSX 10.3, *)
public var __unsafeComponents: UnsafePointer<CGFloat>? { get }
Isn't this what you are looking for?
I can write something like this:
public var cgColour: CGColor {
get {
return CGColor(red: self.colourRed, green: self.colourGreen, blue: self.colourBlue, alpha: self.colourAlpha)
}
set {
if let comps = newValue.__unsafeComponents, newValue.numberOfComponents == 4 {
self.colourRed = comps[0]
self.colourGreen = comps[1]
self.colourBlue = comps[2]
self.colourAlpha = comps[3]
}
}
}
It works as I expect, but I'm not sure it's as you expect, or Apple would treat this as using a private API. (Apple's latest documentation of CGColor does not contain double-underscore leaded symbols.)
extension UIColor {
var all4Components:(red:CGFloat, green:CGFloat, blue: CGFloat, alpha:CGFloat) {
let components = self.cgColor.components!
let red = components[0]
let green = components[1]
let blue = components[2]
let alpha = components[3]
return (red:red, green:green, blue: blue, alpha:alpha)
}
}

PRTweenOperation timingFunction member not found in Swift

I'm trying to use the PRTween library in a Swift iPhone app.
Original example code from GitHub:
PRTweenPeriod *period = [PRTweenPeriod periodWithStartValue:100 endValue:200 duration:3];
PRTweenOperation *operation = [[PRTweenOperation new] autorelease];
operation.period = period;
operation.target = self;
operation.timingFunction = &PRTweenTimingFunctionLinear;
My Swift port:
var period = PRTweenPeriod.periodWithStartValue(100, endValue: 200, duration: 3) as PRTweenPeriod
var operation = PRTweenOperation()
operation.period = period
operation.target = self
operation.timingFunction = PRTweenTimingFunctionLinear
Xcode is giving me this error:
'PRTweenOperation' does not have a member named 'timingFunction'
I'm not sure how to fix this. I can clearly see the member definition in PRTween.h. I'm thinking it might be related to the fact that this is where the definition of PRTweenTimingFunction takes me.
typedef CGFloat(*PRTweenTimingFunction)(CGFloat, CGFloat, CGFloat, CGFloat);
Has anyone else seen an error like this? Any suggestions for fixes?
P.S. I'm not really sure what to call that typedef. Is it a function pointer?
EDIT
As a workaround, I used this code that does not ask for a timing function:
let period = PRTweenPeriod.periodWithStartValue(100, endValue: 200, duration: 2) as PRTweenPeriod
PRTween.sharedInstance().addTweenPeriod(period,
updateBlock: { (p: PRTweenPeriod!) in
NSLog("\(Int(p.tweenedValue))"
},
completionBlock: { NSLog("Completed tween") })
Yes, that's a function pointer. This is a current limitation of C interoperability:
Note that C function pointers are not imported in Swift.
You might consider filing a bug if you'd like this to work. (Note that block-based APIs are fine and work with Swift closures.)

How to port a module in Objective-C to Swift?

After experimenting with a few little Swift programs, I decided my next step was to port a single module in an Objective-C program into Swift to see what steps were required. I had a number of issues, so I thought I'd post my process and results here in case others might find it useful.
I also created a table to help me remember the different conversions. Unfortunately, StackOverflow doesn't support tables, so I posted these conversions as a Github gist here.
Although Apple will undoubtedly provide an Xcode Refactor to convert from Objective-C to Swift, converting one manually is a great way to get familiar with the differences between the two languages. There is so much 'muscle memory' involved in a language you know well, and this is a great way to get familiar with the new syntax. As promised by Apple, it turns out the languages share so many common ideas, that it's mostly a mechanical process (as opposed to porting from, say C++ or even traditional C).
Note that this process uses none of the exciting new features of Swift, it only gets the code straight across. I should mention that moving to Swift will restrict any backwards compatability to iOS 7 or OS X 10.9. I also ran into a couple of issues (with workarounds below) that I'm sure are just due to the first beta release status of the project, so may not be required in future versions.
I chose iPhoneCoreDataRecipes and picked a module that didn’t rely on a lot of others: IngredientDetailViewController. If you'd like to follow along, check out my "answer" below.
Hope this is of use.
0) Download a copy of the project here and open Recipes.xcodeproj in Xcode version 6.
1) Choose File>New File…>iOS Source>Swift File> IngredientDetailViewController (Folder: Classes, Group: Recipe View Controllers)
2) Reply Yes to “Would you like to configure an Objective-C bridging header?”
3) Copy the first three lines below from Recipes_Prefix.pch and the next three from IngredientDetailViewController.m into Recipes-Bridging-Header.h. If you do further files, obviously don't duplicate lines, and remove any files that you've converted to Swift. I haven't found any where that documents the need for the Cocoa lines, given that they're imported in the swift file, but ...
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "Recipe.h"
#import "Ingredient.h"
#import "EditingTableViewCell.h"
4) Copy/paste the text from both the IngredientDetailViewController.h file and the IngredientDetailViewController.m files into IngredientDetailViewController.swift.
5) Delete both IngredientDetailViewController.h and .m files from project.
6) Do a global Find-and-Replace from #import "IngredientDetailViewController.h" to #import "Recipes-Swift.h" (Only one conversion in this case, and again for further files, don't duplicate this line in your Objective-C modules.)
7) Check the Project>Targets>Recipes>Build Settings Runpath Search Paths. If it shows $(inherited), remove this line or you'll get an error on launch about "no image found"
8) Convert Objective-C syntax in IngredientDetailViewController.swift to Swift. See the GitHub Gist mentioned above the substitutions required, or below for my converted version.
9) You may need to update the IB links. Do a Find>Find in Files on IngredientDetailViewController and select the one in Interface Builder. Open the Identity Inspector in the right-hand column. Select IngredientDetailViewController in the Class field, type xxx or something and tab.
10) Build and Run. Note that after going into a recipe, you must tap Edit and then the info button of an ingredient to activate IngredientDetailViewController
12) Congrats on building your first mixed Swift/Objective-C program!
Here's my cut at this particular module:
``
class IngredientDetailViewController: UITableViewController {
var recipe: Recipe!
var ingredient: Ingredient! {
willSet {
if let newIngredient = newValue {
self.ingredientStr = newIngredient.name
self.amountStr = newIngredient.amount
} else {
self.ingredientStr = ""
self.amountStr = ""
}
}
}
init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil?)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
init(style: UITableViewStyle) {
super.init(style: style)
}
// MARK: table's data source
var ingredientStr: String?
var amountStr: String?
// view tags for each UITextField
let kIngredientFieldTag = 1
let kAmountFieldTag = 2
override func viewDidLoad () {
super.viewDidLoad()
self.title = "Ingredient"
self.tableView.allowsSelection = false
self.tableView.allowsSelectionDuringEditing = false
}
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let IngredientsCellIdentifier = "IngredientsCell"
let cell = tableView.dequeueReusableCellWithIdentifier(IngredientsCellIdentifier, forIndexPath: indexPath ) as EditingTableViewCell
if (indexPath.row == 0) {
// cell ingredient name
cell.label.text = "Ingredient"
cell.textField.text = self.ingredientStr
cell.textField.placeholder = "Name"
cell.textField.tag = kIngredientFieldTag
}
else if (indexPath.row == 1) {
// cell ingredient amount
cell.label.text = "Amount"
cell.textField.text = self.amountStr
cell.textField.placeholder = "Amount"
cell.textField.tag = kAmountFieldTag
}
return cell
}
#IBAction func save (sender: AnyObject!) {
if let context = self.recipe.managedObjectContext {
if (!self.ingredient) {
self.ingredient = NSEntityDescription.insertNewObjectForEntityForName("Ingredient",
inManagedObjectContext:context) as Ingredient
self.recipe.addIngredientsObject(self.ingredient)
self.ingredient.displayOrder = self.recipe.ingredients.count
}
// update the ingredient from the values in the text fields
let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow:0, inSection:0)) as EditingTableViewCell
self.ingredient.name = cell.textField.text
// save the managed object context
var error: NSError? = nil
if !context.save( &error) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application, although it may be
useful during development. If it is not possible to recover from the error, display
an alert panel that instructs the user to quit the application by pressing the Home button.
*/
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
}
// if there isn't an ingredient object, create and configure one
self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
}
#IBAction func cancel(sender: AnyObject!) {
self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
}
func textFieldDidEndEditing(textField:UITextField) {
// editing has ended in one of our text fields, assign it's text to the right
// ivar based on the view tag
//
switch (textField.tag)
{
case kIngredientFieldTag:
self.ingredientStr = textField.text
case kAmountFieldTag:
self.amountStr = textField.text
default:
break
}
}
}