Add Stickers Background Color With Swift - ios

I just made my first iMessage Stickers with Swift but I can't change the background color in the box where my stickers displays. I don't want it to be white. How can I do this? and where do I put the code?
I created a new Swift file under MessagesExtensions and used this code. I added all my images and connected them to the class in Storyboard. I have tried all type of codes that I have found online including this:
self.view.backgroundColor = UIColor.black
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "Pattern")!)
I am a newborn to this and maybe I did not add the code in the correct place?
I did not change the MessagesViewController.Swift code but am I supposed to?
Anyways, here is the code that I used:
var stickers :[MSSticker]!
var stickerNames = ["Brokkoli8","Carrot2","Eggplant4","Gresskar9","Lettuce6","Mushroom3","Onion10","Paprika2","Potato5","Tomato7"]
override func viewDidLoad() {
super.viewDidLoad()
self.stickers = [MSSticker]()
self.stickers = loadStickers()
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return self.stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return self.stickers[index]
}
private func loadStickers() -> [MSSticker] {
var stickers = [MSSticker]()
for stickerName in self.stickerNames {
guard let url = Bundle.main.url(forResource: stickerName,withExtension: "png") else {
fatalError("resource does not exist")
}
let sticker = try! MSSticker (contentsOfFileURL: url,
localizedDescription: stickerName)
stickers.append(sticker)
}
return stickers
}
}

check out wwdc presentation on this: https://developer.apple.com/videos/play/wwdc2016/204/
particularly start on 20 min.
in short: you need to subclass MSStickerBrowserView on which you can set backgroundColor

Related

Am I implementing the tableviewdatasource correctly to get downloaded data to show?

I am developing a small app to connect to my site, download data via a PHP web service, and display it in a table view. To get started I was following a tutorial over on Medium by Jose Ortiz Costa (Article on Medium).
I tweaked his project and got it running to verify the Web service was working and able to get the data. Once I got that working, I started a new project and tried to pull in some of the code that I needed to do the networking and tried to get it to display in a tableview in the same scene instead of a popup scene like Jose's project.
This is where I am running into some issues, as I'm still rather new to the swift programming language (started a Udemy course and have been picking things up from that) getting it to display in the table view. I can see that the request is still being sent/received, but I cannot get it to appear in the table view (either using my custom XIB or a programmatically created cell). I thought I understood how the code was broken down, and even tried to convert it from a UITableViewController to a UITableviewDataSource via an extension of the Viewcontroller.
At this point, I'm pretty stumped and will continue to inspect the code and tweak what I think might be the root cause. Any pointers on how to fix would be really appreciated!
Main Storyboard Screenshot
Struct for decoding my data / Lead class:
import Foundation
struct Lead: Decodable {
var id: Int
var name: String
var program: String
var stage: String
var lastAction: String
}
class LeadModel {
weak var delegate: Downloadable?
let networkModel = Network()
func downloadLeads(parameters: [String: Any], url: String) {
let request = networkModel.request(parameters: parameters, url: url)
networkModel.response(request: request) { (data) in
let model = try! JSONDecoder().decode([Lead]?.self, from: data) as [Lead]?
self.delegate?.didReceiveData(data: model! as [Lead])
}
}
}
ViewController:
import UIKit
class LeadViewController: UIViewController {
// Buttons
#IBOutlet weak var newButton: UIButton!
#IBOutlet weak var firstContactButton: UIButton!
#IBOutlet weak var secondContactButton: UIButton!
#IBOutlet weak var leadTable: UITableView!
let model = LeadModel()
var models: [Lead]?
override func viewDidLoad() {
super.viewDidLoad()
//Make Buttons rounded
newButton.layer.cornerRadius = 10.0
firstContactButton.layer.cornerRadius = 10.0
secondContactButton.layer.cornerRadius = 10.0
//Delegate
model.delegate = self
}
//Send request to web service based off Buttons Name
#IBAction func findLeads(_ sender: UIButton) {
let new = sender.titleLabel?.text
let param = ["stage": new!]
print ("findLead hit")
model.downloadLeads(parameters: param, url: URLServices.leads)
}
}
extension LeadViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print ("number of sections hit")
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let _ = self.models else {
return 0
}
print ("tableView 1 hit")
return self.models!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object from LeadCell
let cell = tableView.dequeueReusableCell(withIdentifier: "leadID", for: indexPath) as! LeadCell
// Lead selection
cell.leadName.text = self.models![indexPath.row].name
cell.actionName.text = self.models![indexPath.row].lastAction
cell.stageName.text = self.models![indexPath.row].stage
cell.progName.text = self.models![indexPath.row].program
print ("tableView 2 hit")
// Return the configured cell
return cell
}
}
extension LeadViewController: Downloadable {
func didReceiveData(data: Any) {
//Assign the data and refresh the table's data
DispatchQueue.main.async {
self.models = data as? [Lead]
self.leadTable.reloadData()
print ("LeadViewController Downloadable Hit")
}
}
}
EDIT
So with a little searching around (okay...A LOT of searching around), I finally found a piece that said I had to set the class as the datasource.
leadTable.dataSource = self
So that ended up working (well after I added a prototype cell with the identifier used in my code). I have a custom XIB that isn't working right now and that's my next tackle point.
You load the data, but don't use it. First, add the following statement to the end of the viewDidLoad method
model.delegate = self
Then add the following LeadViewController extension
extension LeadViewController: Downloadable {
func dicReceiveData(data: [Lead]) {
DispatchQueue.main.async {
self.models = data
self.tableView.reloadData()
}
}
}
And a couple of suggestions:
It is not a good practice to use the button title as a network request parameter:
let new = sender.titleLabel?.text
let param = ["stage": new!]
It is better to separate UI and logic. You can use the tag attribute for buttons (you can configure it in the storyboard or programmatically) to check what button is tapped.
You also have several unnecessary type casts in the LeadModel class. You can change
let model = try! JSONDecoder().decode([Lead]?.self, from: data) as [Lead]?
self.delegate?.didReceiveData(data: model! as [Lead])
to
do {
let model = try JSONDecoder().decode([Lead].self, from: data)
self.delegate?.didReceiveData(data: model)
}
catch {}

How can you customize UIActivityViewController to ONLY include custom UIActivity types?

I'm trying to customize the native ios UIActivityViewController so it will restrict every UIActivity except for the custom UIActivity types that I initialize the UIActivityViewController with.
Essentially, I only want the user to see my four custom UIActivitys.
let items = ["Hello world"]
let activitySheet = CustomUIActivityViewController(
activityItems: items,
applicationActivities: [
CustomMailUIActivity(),
CustomMessagesUIActivity(),
CustomTwitterUIActivity(),
CustomFacebookUIActivity()
]
)
I understand that you can use activitySheet.excludedActivityTypes = [] to exclude types that you do not need, however you are not able to exclude third party applications such as Slack.
I was wondering if there was any way to get around that and ONLY include custom applicationActivies. It would also be great to remove the "More" button that appears on the share sheet as well. Any help would be greatly appreciated.
This is what I'm trying to achieve.
screenshot
My colleague was able to figure this out. For anyone wondering how to display only the applicationActivities (custom activities) on the share sheet, this is how I did it.
For activityItems rather than creating an array of strings, create an array of your custom data objects. Then override the canPerform(withActivityItems activityItems: [Any]) method in your custom UIActivity (subclass of UIActivity) to return true if activityItems[o] is CustomItem. Since your activityItems are custom, system will not display other apps on the share sheet. That's it.
In the example below, only your CustomUIActivity will be displayed on the share sheet.
let items = [CustomItem("Hello world")]
let activitySheet = UIActivityViewController(
activityItems: items,
applicationActivities: [
CustomUIActivity()
]
)
class CustomItem {
let value: String
required init(value: String) {
self.value = value
}
func getValue() -> String {
return self.value
}
}
#objc(CustomUIActivity)
class CustomUIActivity: UIActivity {
override class var activityCategory: UIActivity.Category {
return .share
}
override var activityType: UIActivity.ActivityType? {
return .customuiactivity
}
override var activityTitle: String? {
return "Custom"
}
override var activityImage: UIImage? {
return UIImage(named: "custom-icon")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
if activityItems.count == 1 && activityItems[0] is CustomItem {
return true
}
return false
}
var textToShare: String?
override func prepare(withActivityItems activityItems: [Any]) {
if let activityItem = activityItems.first as? CustomItem {
self.textToShare = activityItem.getValue
}
}
override func perform() {
// perform your custom activity with `textToShare`
activityDidFinish(true)
}
}
extension UIActivity.ActivityType {
static let customuiactivity =
UIActivity.ActivityType("com.ceylonese.mobile.customuiactivity")
}

How to count how many times all classes are called

I want the user to be able to know how many times they have visited each class. Then add together the totals from each page together to form a group sum. I want to print the total sum in the log file in each of the two view controllers. So just one string should be printed.
class oneV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set(true, forKey: "VC1")
}
}
class twoV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set(true, forKey: "VC2")
}
}
If you mean visited each view controller, when you say visited each class. Then i'd recommend you do it viewDidAppear.
class YourViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let key = String(describing: type(of: self))
let count = UserDefaults.standard.value(forKey: key) as? Int ?? 0
UserDefaults.standard.set(value + 1, forKey: key)
}
}
To make it simpler, you could use an extension on UIViewController.
extension UIViewController {
func updateVisitCount() {
let key = String(describing: type(of: self))
let count = UserDefaults.standard.value(forKey: key) as? Int ?? 0
UserDefaults.standard.set(count + 1, forKey: key)
}
}
Or, if you need this for every view controller that you create, then you can create a base view controller which you would use everywhere instead of UIViewController.
class BaseViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateVisitCount()
}
}
The most automatic solution would be inject the accounting call in viewDidLoad without replacing the original viewDidLoad.
Here demo purpose i've created a sample Playground
import UIKit
import PlaygroundSupport
extension UIViewController {
#objc dynamic func substitutedViewDidAppear() {
print("This is injected code in view did appear")
substitutedViewDidAppear() // it may look like recursive, but it isn't, actually it calls the original `viewDidAppear` method.
}
class func swizzle() {
let originalMethod = class_getInstanceMethod(UIViewController.self, #selector(viewDidAppear(_:)))
let substitutedMethod = class_getInstanceMethod(UIViewController.self, #selector(substitutedViewDidAppear))
if let originalMethod = originalMethod,
let substitutedMethod = substitutedMethod {
print("swizzled")
method_exchangeImplementations(originalMethod, substitutedMethod)
} else {
print("not swizzled")
}
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
print("view loaded")
}
}
// Swizzle
UIViewController.swizzle() // call this in #top of didFinishLaunchingWithOptions
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Output:
swizzled
view loaded
This is injected code in view did appear
Now in the substitutedViewDidAppear upper portion inject your counting code as #Rakesha Shastri Suggested, call the updateVisitCount method inside of substitutedViewDidAppear & place the UIViewController.swizzle() in applicationDidFinishLaunchingWithOptions before creating the root window.
Create a static variable. A static variable is a type of class, not object therefore throughout all objects a variable maybe maintained. I think this example may better explain how this works. Click here
In ViewDidLoad method call this function :
func updateVisitingCounter() {
var counter = UserDefaults.standard.integer(forKey: "firstPageCounter")
counter += 1
UserDefaults.standard.set(counter, forKey: "firstPageCounter")
}
You may set declare variables at project scope "outside of classes"
var vc1Count = 0
class oneV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
vc1Count = vc1Count+1
}
}
var vc2Count = 0
class twoV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
vc2Count = vc2Count+1
}
}
you can also declare these variables at a common place.
As per your requirements its kind of Analytics on app usage. You can implement in 2 ways
By storing data with screen visit in local DB and show it on Analysis Page or on summery page.
Sample code for storing Screen details in DB:
==> Create your Entity for Screen capture.
ScreenVisit.
==> Store Data with screen name.
let entity = NSEntityDescription.entity(forEntityName: "ScreenVisit", in: context)
let newVisit = NSManagedObject(entity: entity!, insertInto: context)
newVisit.setValue("HomeScreen", forKey: "screenname")
newVisit.setValue("1", forKey: "visited")
do {
try context.save()
} catch {
print("Failed saving")
}
==> Fetch data where you required.
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ScreenVisit")
//request.predicate = NSPredicate(format: <Your Filter Logic>)
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "screenname") as! String)
print(data.value(forKey: "visited") as! String)
}
} catch {
print("Failed")
}
You can use any 3rd party library like Google analytics, Crashlytics for tracking your user actions.
Ref Links :
Firebase iOS analytics
Crashlytics
but as per my experience 2nd way is more convenient and powerful.
All depends on your requirements.
Hope this will helps you to get your user action captured.

How to print a UIWebView via UIActivityViewController?

I have a view controller containing a UIWebView and a toolbar with an action/share button. This initializes and presents a UIActivityViewController object. Depending on whether I supply the activityItems parameter with either the web view's URL or the URL's corresponding absoluteString, different actions are offered, but the Print option is never shown (nor offered in the "more" section).
I do know how to print the web view contents explicitly using UIPrintInfo and UIPrintInteractionController, but that would be a separate toolbar button whereas I want to simply include the system's Print option into the activity button row. I assume printing a web view does not need any explicit coding.
What can I do?
You can create Custom Activity For UIActivityCotnroller like this,
import UIKit
protocol CustomActivityDelegate : NSObjectProtocol
{
func performActionCompletion(actvity: CustomActivity)
}
class CustomActivity: UIActivity {
var delegate: CustomActivityDelegate?
override class var activityCategory: UIActivityCategory {
return .action
}
override var activityType: UIActivityType? {
guard let bundleId = Bundle.main.bundleIdentifier else {return nil}
return UIActivityType(rawValue: bundleId + "\(self.classForCoder)")
}
override var activityTitle: String? {
return "You title"
}
override var activityImage: UIImage? {
return <Your activity image >
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
return true
}
override func prepare(withActivityItems activityItems: [Any]) {
//
}
override func perform() {
self.delegate?.performActionCompletion(actvity: self)
activityDidFinish(true)
}
}
You can initialize this activity some thing like this
let customActivity = CustomActivity()
customActivity.delegate = self
And you can add this custom activity while preparing UIActivityController
let activityViewController : UIActivityViewController = UIActivityViewController(activityItems: [customActivity], applicationActivities: nil)
and you will also need to implement the call back method
func performActionCompletion(actvity: CustomActivity)
{
//Perform you task
}
Note : This is just pseudo code, might contain error or syntax problems

SFSafariViewController: how to provide custom activities?

The WWDC session of Safari View Controller mentioned that apps could provide custom activities through the method func safariViewController(controller: SFSafariViewController, activityItemsForURL URL: NSURL, title: String?) -> [UIActivity] of the delegate SFSafariViewControllerDelegate. I have tried to implement this method, but it is not called after I present the SFSafariViewCntroller. I also implemented another optional method of that delegate, func safariViewControllerDidFinish(_: SFSafariViewController), which does get called. I tried to add the "#objc" keyword to my method (required by some other protocols), but it seems not to change anything.
I am wondering what could go wrong.
Thanks!
Here's the example code for your reference. In your main view:
func safariViewController(controler: SFSafariViewController, activityItemsForURL: NSURL, title: String?) -> [UIActivity] {
//global variable for the url to be shared
webPageUrl = activityItemsForURL.absoluteString
//global variable for the title to be shared
webPageTitle = title!
let wcActivity = WeChatActivity()
let wcMoment = WeChatMoment()
return [wcActivity, wcMoment]
}
Custom activities 1
import UIKit
class WeChatActivity : UIActivity{
override init() {
self.text = ""
}
var text:String?
override func activityType()-> String {
return "WeChat"
}
override func activityImage()-> UIImage?
{
return UIImage(named: "WeChat")!
}
override func activityTitle() -> String
{
return "微信好友"
}
override class func activityCategory() -> UIActivityCategory{
return UIActivityCategory.Action
//you can change to .Share and it'll appear in the share line
}
func getURLFromMessage(message:String)-> NSURL
{
var url = "whatsapp://"
if (message != "")
{
url = "\(url)send?text=\(message)"
}
return NSURL(string: url)!
}
override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool {
return true;
}
override func performActivity() {
shareToWeChat("ftcweixin://?url=\(webPageUrl)&title=\(webPageTitle)&description=\(webPageDescription)&img=\(webPageImageIcon)&to=chat")
}
}
Custom Activity 2:
import UIKit
class WeChatMoment : UIActivity{
override init() {
self.text = ""
}
var text:String?
override func activityType()-> String {
return "WeChatMoment"
}
override func activityImage()-> UIImage?
{
return UIImage(named: "Moment")!
}
override func activityTitle() -> String
{
return "微信朋友圈"
}
override class func activityCategory() -> UIActivityCategory{
return UIActivityCategory.Action
}
func getURLFromMessage(message:String)-> NSURL
{
var url = "whatsapp://"
if (message != "")
{
url = "\(url)send?text=\(message)"
}
return NSURL(string: url)!
}
override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool {
return true;
}
override func performActivity() {
shareToWeChat("ftcweixin://?url=\(webPageUrl)&title=\(webPageTitle)&description=\(webPageDescription)&img=\(webPageImageIcon)&to=moment")
}
}
You'll be able to see the two new icons in the action line of the action sheet. You can also change it to appear in the share line, as explained in the code.
One final note, there are pitfalls for WeChat sharing on Safari View, as WeChat doesn't conform to Safari's sharing standard. You can click the WeChat Share icon and WeChat will be able to share. But you can only get the page title and url in Safari View's page, unlike with WKWebView where you can get everything using evaluateJavaScript. So you'll need to get the share image and description (for sharing to friends) from other places.

Resources