How to make a notification badge on the table view?
If there is new data it will display the badge on the table view, and if there is new data again, the badge will automatically be added.
I can strongly recommend you use the following Framework: swift-badge
use_frameworks!
target 'Your target name'
pod 'BadgeSwift', '~> 4.0'
Easy to use:
let badge = BadgeSwift()
view.addSubview(badge)
// Position the badge ...
Customization:
// Text
badge.text = "2"
// Insets
badge.insets = CGSize(width: 12, height: 12)
// Font
badge.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
// Text color
badge.textColor = UIColor.yellow
// Badge color
badge.badgeColor = UIColor.black
// Shadow
badge.shadowOpacityBadge = 0.5
badge.shadowOffsetBadge = CGSize(width: 0, height: 0)
badge.shadowRadiusBadge = 1.0
badge.shadowColorBadge = UIColor.black
// No shadow
badge.shadowOpacityBadge = 0
// Border width and color
badge.borderWidth = 5.0
badge.borderColor = UIColor.magenta
// Customize the badge corner radius.
// -1 if unspecified. When unspecified, the corner is fully rounded. Default: -1.
badge.cornerRadius = 10
Also. If you don't want to use pods, here is the full and ready to use class It also includes functions I have created:
import UIKit
/**
Badge view control for iOS and tvOS.
Project home: https://github.com/marketplacer/swift-badge
*/
#IBDesignable public class BadgeSwift: UILabel {
/// Background color of the badge
#IBInspectable public var badgeColor = Colors.red {
didSet {
setNeedsDisplay()
}
}
/// Width of the badge border
#IBInspectable public var borderWidth: CGFloat = 0 {
didSet {
invalidateIntrinsicContentSize()
}
}
/// Color of the bardge border
#IBInspectable public var borderColor = Colors.white {
didSet {
invalidateIntrinsicContentSize()
}
}
/// Badge insets that describe the margin between text and the edge of the badge.
#IBInspectable public var insets: CGSize = CGSize(width: 5, height: 2) {
didSet {
invalidateIntrinsicContentSize()
}
}
// MARK: Badge shadow
// --------------------------
/// Opacity of the badge shadow
#IBInspectable public var shadowOpacityBadge: CGFloat = 0.5 {
didSet {
layer.shadowOpacity = Float(shadowOpacityBadge)
setNeedsDisplay()
}
}
/// Size of the badge shadow
#IBInspectable public var shadowRadiusBadge: CGFloat = 0.5 {
didSet {
layer.shadowRadius = shadowRadiusBadge
setNeedsDisplay()
}
}
/// Color of the badge shadow
#IBInspectable public var shadowColorBadge = Colors.black {
didSet {
layer.shadowColor = shadowColorBadge.cgColor
setNeedsDisplay()
}
}
/// Offset of the badge shadow
#IBInspectable public var shadowOffsetBadge: CGSize = CGSize(width: 0, height: 0) {
didSet {
layer.shadowOffset = shadowOffsetBadge
setNeedsDisplay()
}
}
/// Initialize the badge view
convenience public init() {
self.init(frame: CGRect())
}
/// Initialize the badge view
override public init(frame: CGRect) {
super.init(frame: frame)
setup()
}
/// Initialize the badge view
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
/// Add custom insets around the text
override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
var insetsWithBorder = actualInsetsWithBorder()
let rectWithDefaultInsets = rect.insetBy(dx: -insetsWithBorder.width, dy: -insetsWithBorder.height)
// If width is less than height
// Adjust the width insets to make it look round
if rectWithDefaultInsets.width < rectWithDefaultInsets.height {
insetsWithBorder.width = (rectWithDefaultInsets.height - rect.width) / 2
}
let result = rect.insetBy(dx: -insetsWithBorder.width, dy: -insetsWithBorder.height)
return result
}
/// Draws the label with insets
override public func drawText(in rect: CGRect) {
layer.cornerRadius = rect.height / 2
let insetsWithBorder = actualInsetsWithBorder()
let insets = UIEdgeInsets(
top: insetsWithBorder.height,
left: insetsWithBorder.width,
bottom: insetsWithBorder.height,
right: insetsWithBorder.width)
let rectWithoutInsets = UIEdgeInsetsInsetRect(rect, insets)
super.drawText(in: rectWithoutInsets)
}
/// Draw the background of the badge
override public func draw(_ rect: CGRect) {
let rectInset = rect.insetBy(dx: borderWidth/2, dy: borderWidth/2)
let path = UIBezierPath(roundedRect: rectInset, cornerRadius: rect.height/2)
badgeColor.setFill()
path.fill()
if borderWidth > 0 {
borderColor.setStroke()
path.lineWidth = borderWidth
path.stroke()
}
super.draw(rect)
}
private func setup() {
textAlignment = NSTextAlignment.center
clipsToBounds = false // Allows shadow to spread beyond the bounds of the badge
}
/// Size of the insets plus the border
private func actualInsetsWithBorder() -> CGSize {
return CGSize(
width: insets.width + borderWidth,
height: insets.height + borderWidth
)
}
/// Draw the stars in interface builder
#available(iOS 8.0, *)
override public func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
setNeedsDisplay()
}
}
func createBadge(_ who: BadgeSwift, _ view: UIView, _ value: String) {
if !view.subviews.contains(who) {
view.addSubview(who)
}
configureBadge(who, view, value)
positionBadge(who, view)
}
func removeBadge(_ who: BadgeSwift, _ view: UIView) {
if view.subviews.contains(who) {
who.removeFromSuperview()
}
}
func configureBadge(_ badge: BadgeSwift, _ view: UIView, _ value: String) {
badge.text = value
badge.insets = CGSize(width: 2, height: 2)
badge.font = UIFont.boldSystemFont(ofSize: 12)
badge.textColor = Colors.white
badge.badgeColor = Colors.badgeRed
}
func positionBadge(_ badge: UIView, _ view: UIView) {
badge.translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint]()
constraints.append(NSLayoutConstraint(
item: badge,
attribute: NSLayoutAttribute.centerY,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.top,
multiplier: 1, constant: 5)
)
constraints.append(NSLayoutConstraint(
item: badge,
attribute: NSLayoutAttribute.centerX,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.right,
multiplier: 1, constant: -5)
)
view.addConstraints(constraints)
}
func calculateCount(_ items: [UITabBarItem]) -> String {
var countInt = 0
for i in items {
if let countString = i.badgeValue {
countInt = countInt + Int(countString)!
}
}
return String(countInt)
}
For your purpose to create a Badge inside a UITableViewCell, you could use:
let badge = BadgeSwift()
createBadge(badge, MyCell, "10")
That would give you a Badge of 10.
Related
I'm looking to make a setting view in my app where I could define 3 map parameters :
Map center initial position
Map initial zoom
Map initial type
Then show an example map and save in into the #AppStorage
My initial position is determined from an array of data which include :
Place name
Place Latitude
Place Longitude
Latitude and Longitude is find via a function which return a Double? :
func getApLat(ApName: String) -> Double?{
guard let foundAirport = FR_airportsDB.first(where: {$0.Icao == ApName}),
let lat = Double(foundAirport.Latitude) else { return nil }
return lat
}
func getApLong(ApName: String) -> Double?{
guard let foundAirport = FR_airportsDB.first(where: {$0.Icao == ApName}),
let longit = Double(foundAirport.Longitude) else { return nil }
return longit
}
Now my settingMapView is defined as follow :
struct MapOptionView: View {
#State private var showingAlert = false
#State private var latDouble = getApLat(ApName: UserDefaults.standard.string(forKey: "MAP_CenterInit") ?? "LFLI")
#State private var longDouble = getApLong(ApName: UserDefaults.standard.string(forKey: "MAP_CenterInit") ?? "LFLI")
#State private var typeExemple: MKMapType = getTypeFromUD()
#State private var exampleZoom: Int = 5
#AppStorage("MAP_CenterInit") private var MapCenterAirport = ""
#AppStorage("MAP_ZoomInit") private var MapZoom = 2
#AppStorage("MAP_TypeInit") private var MapType = 1
var body: some View {
List{
Section(header: Text("Initial location")){
HStack{
TextField("ICAO", text: $MapCenterAirport)
.padding()
.background(.white)
.cornerRadius(20.0)
.keyboardType(.default)
.textCase(.uppercase)
.onReceive(Just(MapCenterAirport)) { inputValue in
if inputValue.count > 4 {
self.MapCenterAirport.removeLast()
}
}
.disableAutocorrection(true)
.textCase(.uppercase)
if MapCenterAirport != "" {
Button {
//Here I update the center data after get it from the function
latDouble = getApLat(ApName: MapCenterAirport)
longDouble = getApLong(ApName: MapCenterAirport)
} label: {
Text("Check")
}
}
}
}
// Section where Zoom is defined
Section(header: Text("Initial zoom")){
HStack {
if MapZoom == 1{
Text("Zoom : Low")
}else if MapZoom == 2{
Text("Zoom : Medium")
}else if MapZoom == 3{
Text("Zoom : Large")
}
Spacer()
Stepper("", value: $MapZoom, in: 1...3)
}.padding(.vertical)
}
// Section where Type is defined
Section(header: Text("Initial type")){
HStack {
if MapType == 1{
Text("Type : Standard")
}else if MapType == 2{
Text("Type : Satellite")
}else if MapType == 3{
Text("Type : Satellite-flyover")
}else if MapType == 4{
Text("Type : Hybrid")
}else if MapType == 5{
Text("Type : Hybrid-flyover")
}
Spacer()
Stepper("", value: $MapType, in: 1...5)
.frame(width: 60)
.padding(.horizontal, 20)
}.padding(.vertical)
}
// Section where Exemple is Show but need to clickButton to update map ...
Section(header: Text("Example")){
Button {
//Here is zoom adaptation regarding stepper choice from $MapZoom
if MapZoom == 1{
exampleZoom = 60000
}else if MapZoom == 2{
exampleZoom = 110000
}else if MapZoom == 3{
exampleZoom = 160000
}
//Here is type adaptation regarding stepper choice from $MapType
if MapType == 1{
typeExemple = .standard
}else if MapType == 2{
typeExemple = .satellite
}else if MapType == 3{
typeExemple = .satelliteFlyover
}else if MapType == 4{
typeExemple = .hybrid
}else if MapType == 5{
typeExemple = .hybridFlyover
}
} label: {
HStack {
Text("Update example")
}
}
//Here is my Map definition
MapViewSetting(mapType: $typeExemple,
funcLat: $latDouble,
funcLong: $longDouble,
funcZoom: $exampleZoom
)
.edgesIgnoringSafeArea(.all)
.frame(height: 300)
}
}.navigationTitle("Map options")
.navigationBarItems(trailing: Button(action: {
showingAlert = true
//Here we save the 3 value into the AppStorage
MapCenterAirport = MapCenterAirport.uppercased()
UserDefaults.standard.set(self.MapCenterAirport, forKey: "MAP_CenterInit")
UserDefaults.standard.set(self.MapZoom, forKey: "MAP_ZoomInit")
UserDefaults.standard.set(self.MapType, forKey: "MAP_TypeInit")
}, label: {
HStack {
Text("Save")
}
})).alert("Informations saved !\nMust have to restart the app to apply it.", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
}
}
And finally I've my map struct defined as follow after following some post find on this forum.
struct MapViewSetting: UIViewRepresentable {
#Binding var mapType: MKMapType
#Binding var funcLat: Double
#Binding var funcLong: Double
#Binding var funcZoom: Int
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: .zero)
let center = CLLocationCoordinate2D(latitude: funcLat, longitude: funcLong)
let region = MKCoordinateRegion(center: center,
latitudinalMeters: CLLocationDistance(funcZoom),
longitudinalMeters: CLLocationDistance(funcZoom)
)
mapView.setRegion(region, animated: true)
mapView.mapType = mapType
mapView.showsScale = true
mapView.showsTraffic = false
mapView.showsCompass = true
mapView.showsUserLocation = false
mapView.showsBuildings = false
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.mapType = self.mapType
}
}
But Two big mistake with my code :
First I got an error here :
MapViewSetting(mapType: $typeExemple,
/*regionFunc: $region,*/
funcLat: $latDouble ?? 46.192001,
funcLong: $longDouble ?? 6.26839,
funcZoom: $exampleZoom
)
On funcLat : and funcLong which say : Cannot convert value of type 'Binding<Double?>' to expected argument type 'Binding<Double>'
And If I define manually the Lat and Long in my code to test the two other parameters I could only change the type but not the Zoom ...
Hope to be as clear as I can
Thanks
I am currently using Google Maps API in SwiftUI, and am trying to get a sheet to appear after a marker infoWindow is tapped programmatically.
In other parts of my app I’m displaying sheets like this, and it’s the same thing I’m trying to achieve here but programmatically:
https://blog.kaltoun.cz/swiftui-presenting-modal-sheet/
Right now I have a function that prints a message when an infoWindow is tapped, but don’t know how to make a SwiftUI view appear within a sheet using the function.
-
Since I’m using SwiftUI, the way I implement the Google Maps API is a little different than in plain Swift.
Here are the basics of my GMView.swift file that handles all the google maps stuff.
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation
struct GoogMapView: View {
var body: some View {
GoogMapControllerRepresentable()
}
}
class GoogMapController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
var zoomLevel: Float = 15.0
let marker : GMSMarker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
// mapView.isHidden = true
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
return GMController()
}
func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {
}
}
Here is the function I'm putting within GMView.swift's UIViewController (GMController) that I’m trying to use to make a view appear within a sheet:
// Function to handle when a marker's infowindow is tapped
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf didTapInfoWindowOfMarker: GMSMarker) {
print("You tapped a marker's infowindow!")
return
}
Here is the view I’m trying to get to appear:
struct SortBy: View {
var body: some View {
VStack(alignment: .leading) {
Text("Sort By")
.font(.title)
.fontWeight(.black)
.padding(.trailing, 6)
Rectangle()
.fill(Color.blue)
.frame(width: 200, height: 200)
}
}
}
Does anyone know how I can get my function above to make a SwiftUI view appear within a sheet?
Use .sheet similarly like here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/
You will need:
In your UIViewControllerRepresentable, add a State object, let's say #State var showModal = false
In your parent view, Use .sheet(isPresented: $showModal) { CONTENT_VIEW(showModal: $showModal) }
In your UIViewControllerRepresentable, also add a Binding for showModal .
In your UIViewControllerRepresentable, use Coordinator to set your UIViewController and GMSMapViewDelegate
In your UIViewController, Now you can access the Binding via owner.showModal
Hope it helps. Good luck.
SwiftUI doesn't appear to support UITabBar. How can I integrate that capability?
Merely wrapping the view like one would a (eg) MKMapView, doesn't work because of its need for deep integration with NavigationView. Using UINavigationView is too un-SwiftUI-ish.
The 'TabbedView' is the closest thing. It can be used similar to the following:
struct TabView : View {
#State private var selection = 1
var body: some View {
TabbedView (selection: $selection) {
InboxList()
.tabItemLabel(selection == 1 ? Image("second") : Image("first"))
.tag(1)
PostsList()
.tabItemLabel(Image("first"))
.tag(2)
Spacer()
.tabItemLabel(Image("first"))
.tag(3)
Spacer()
.tabItemLabel(Image("second"))
.tag(4)
}
}
}
If you aren't happy with TabbedView, you can always roll your own! Here's a quick base implementation:
import SwiftUI
struct ContentView : View {
let tabs = [TabItemView(title: "Home", content: { Text("Home page text") }), TabItemView(title: "Other", content: { Text("Other page text") }), TabItemView(title: "Pictures", content: { Text("Pictures page text") })]
var body: some View {
TabBar(tabs: tabs, selectedTab: tabs[0])
}
}
struct TabItemView<Content> : Identifiable where Content : View {
var id = UUID()
var title: String
var content: Content
init(title: String, content: () -> Content) {
self.title = title
self.content = content()
}
var body: _View { content }
typealias Body = Never
}
struct TabBar<Content>: View where Content : View {
let tabButtonHeight: Length = 60
var tabs: [TabItemView<Content>]
#State var selectedTab: TabItemView<Content>
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.selectedTab.content.frame(width: geometry.size.width, height: geometry.size.height - self.tabButtonHeight)
Divider()
HStack(spacing: 0) {
ForEach(self.tabs) { tab in
Button(action: { self.selectedTab = tab}) {
Text(tab.title)
}.frame(width: geometry.size.width / CGFloat(Double(self.tabs.count)), height: self.tabButtonHeight)
}
}
.background(Color.gray.opacity(0.4))
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
UITabBar seems to be working now on Xcode 13.3, SwiftUI 3, iOS15+
It works even though I didn't import UIKit, not sure if that has any effect but it's working for me
struct LandingView: View {
#Binding var selectedTab: String
//hiding tab bar
init(selectedTab: Binding<String>) {
self._selectedTab = selectedTab
UITabBar.appearance().isHidden = true
}
var body: some View {
//Tab view with tabs
TabView(selection: $selectedTab) {
//Views
Home()
.tag("Home")
PlaylistView()
.tag("My Playlists")
HistoryView()
.tag("History")
}
}
}
I misstated the question as I was trying to make ToolBar... below is the code I ended up with... thanks to all.
struct ToolBarItem : Identifiable {
var id = UUID()
var title : String
var imageName : String
var action: () -> Void
}
struct TooledView<Content> : View where Content : View{
var content : Content
var items : [ToolBarItem]
let divider = Color.black.opacity(0.2)
init(items : [ToolBarItem], content: () -> Content){
self.items = items
self.content = content()
}
var body : some View{
VStack(spacing: 0){
self.content
self.divider.frame(height: 1)
ToolBar(items: self.items).frame(height: ToolBar.Height)
}
}
}
struct ToolBar : View{
static let Height : Length = 60
var items : [ToolBarItem]
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0){
ForEach(self.items){ item in
Button(action: item.action){
Image(systemName: item.imageName).imageScale(.large)
Text(item.title).font(.caption)
}.frame(width: geometry.size.width / CGFloat(Double(self.items.count)))
}
}
.frame(height: ToolBar.Height)
.background(Color.gray.opacity(0.10))
}
}
}
I wrote this code in Swift 3.0 but draw method is not called from func drawBorder by needsDisplay = true; drawBorder is called by clicking button in another view.
Thanks for any hint.
class clsDrawView: NSView {
private var redraw = false
var border = NSBezierPath()
var color = NSColor()
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
if redraw {
color.setStroke()
border.stroke()
}
}
func drawBorder() {
redraw = true
color = NSColor.blue
border.lineWidth = CGFloat(10)
border.move(to: NSPoint(x: 20, y: 20))
border.line(to: NSPoint(x: 50, y: 50))
needsDisplay = true
}
}
Try to call setNeedsDisplay(_:) method on your view, this should call draw(_:) method. Never call draw(_:) method by yourself.
Finally I solved it via notification
class clsDrawView: NSView {
private var draw = false
private var border = NSBezierPath()
var color = NSColor()
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NotificationCenter.default.addObserver(self, selector: #selector(self.drawBorder(_:)), name: NSNotification.Name(rawValue: "drawBorder"), object: nil)
// Drawing code here.
if draw {
color.setStroke()
border.stroke()
}
}
func drawBorder(_ notification: NSNotification) {
draw = true
color = NSColor.black
NSBezierPath.setDefaultLineWidth(4)
border.lineWidth = CGFloat(10)
border = NSBezierPath(rect: self.bounds)
needsDisplay = true
}
}
Notification is postec from ViewController like this
#IBAction func btnDraw(_ sender: NSButton) {
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "drawBorder"), object: sender)
}
What I need:
A DataTemplate of Image, Title, Description.
What I tried:
I tried ImageCell but it's image size cannot be controlled and hence the Image goes out of screen (on Windows Phone 8.1)
I tried creating CustomRenderer, wasn't able to do it for WP8.1 (couldnt extract the UI Controls from DataTemplate in public override Windows.UI.Xaml.DataTemplate GetTemplate(Cell cell)
I created a ViewCell.View, put Image + two Labels in a Grid. BUT again, it doesnt work properly on WP8.1. When I scroll back up, the Text gets cut.
<ViewCell>
<ViewCell.View>
<StackLayout Padding="20,10,20,10">
<Grid ColumnSpacing="10" RowSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Label Text="{Binding Title}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" FontSize="Medium"
XAlign="Start" HorizontalOptions="StartAndExpand" YAlign="Start" LineBreakMode="CharacterWrap" MinimumHeightRequest="60"
FontAttributes="Bold"
TextColor="Black">
</Label>
<Image Source="{Binding Image}"
Grid.Row="1" Grid.RowSpan="2" Grid.Column="0"
Aspect="AspectFill" HorizontalOptions="Start">
</Image>
<Label Text="{Binding Description}"
Grid.Row="1" Grid.RowSpan="2" Grid.Column="1"
FontSize="14" XAlign="Start" HorizontalOptions="StartAndExpand" YAlign="Start" LineBreakMode="CharacterWrap"
TextColor="Black">
</Label>
</Grid>
</StackLayout>
</ViewCell.View>
</ViewCell>
So, how do I create my own Cell or Control more attributes of ImageCell
This is a code that I once wrote to make a custom ViewCell, it has a label and an image, hope this helps you (I tested this on iOS, Android and WP):
public class ViewCellExtend : ViewCell
{
Label menuText;
Image menuImagen;
public ViewCellExtend()
{
menuText = new Label();
menuImagen = new Image()
{
Aspect = Aspect.AspectFit,
HeightRequest = 30,
WidthRequest = 30
};
StackLayout stContenedor = new StackLayout
{
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Spacing = 20,
Children =
{
CreateCellBlock()
}
};
View = stContenedor;
}
Xamarin.Forms.Layout CreateCellBlock()
{
StackLayout st2 = new StackLayout
{
Orientation = StackOrientation.Horizontal,
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children =
{
new StackLayout
{
Padding = new Thickness(15,0,10,0),
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center,
Children =
{
menuImagen
}
},
new StackLayout
{
Padding = new Thickness(0,5,10,0),
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center,
Children =
{
menuText
}
}
}
};
st2.SetBinding(Layout.BackgroundColorProperty, new Binding("BackgroundColor"));
return st2;
}
public static BindableProperty BindableTextProperty = BindableProperty.Create<ViewCellExtend, string>(ctrl =>
ctrl.TextProperty,
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay,
propertyChanging: (bindable, oldValue, newValue) =>
{
var ctrl = (ViewCellExtend)bindable;
ctrl.TextProperty = newValue;
});
public string TextProperty
{
get { return (string)GetValue(BindableTextProperty); }
set
{
SetValue(BindableTextProperty, value);
menuText.Text = value;
}
}
public static BindableProperty BindableImageProperty = BindableProperty.Create<ViewCellExtend, ImageSource>(ctrl =>
ctrl.ImageProperty,
defaultValue: default(ImageSource),
defaultBindingMode: BindingMode.Default,
propertyChanging: (bindable, oldValue, newValue) =>
{
var ctrl = (ViewCellExtend)bindable;
ctrl.ImageProperty = newValue;
}
);
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
System.Diagnostics.Debug.WriteLine(BindingContext.ToString());
}
public ImageSource ImageProperty
{
get { return (ImageSource)GetValue(BindableImageProperty); }
set
{
SetValue(BindableImageProperty, value);
menuImagen.Source = value;
}
}
}
Here's a very good demonstration of using custom ViewCellRenderer:
https://github.com/xamarin/xamarin-forms-samples/tree/master/WorkingWithListviewNative
Android:
public class NativeAndroidCellRenderer : ViewCellRenderer
{
protected override Android.Views.View GetCellCore (Xamarin.Forms.Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
var x = (NativeCell)item;
var view = convertView;
if (view == null) {// no view to re-use, create new
view = (context as Activity).LayoutInflater.Inflate (Resource.Layout.NativeAndroidCell, null);
} else { // re-use, clear image
// doesn't seem to help
//view.FindViewById<ImageView> (Resource.Id.Image).Drawable.Dispose ();
}
view.FindViewById<TextView>(Resource.Id.Text1).Text = x.Name;
view.FindViewById<TextView>(Resource.Id.Text2).Text = x.Category;
// grab the old image and dispose of it
// TODO: optimize if the image is the *same* and we want to just keep it
if (view.FindViewById<ImageView> (Resource.Id.Image).Drawable != null) {
using (var image = view.FindViewById<ImageView> (Resource.Id.Image).Drawable as BitmapDrawable) {
if (image != null) {
if (image.Bitmap != null) {
//image.Bitmap.Recycle ();
image.Bitmap.Dispose ();
}
}
}
}
// If a new image is required, display it
if (!String.IsNullOrWhiteSpace (x.ImageFilename)) {
context.Resources.GetBitmapAsync (x.ImageFilename).ContinueWith ((t) => {
var bitmap = t.Result;
if (bitmap != null) {
view.FindViewById<ImageView> (Resource.Id.Image).SetImageBitmap (bitmap);
bitmap.Dispose ();
}
}, TaskScheduler.FromCurrentSynchronizationContext() );
} else {
// clear the image
view.FindViewById<ImageView> (Resource.Id.Image).SetImageBitmap (null);
}
return view;
}
}
iOS:
public class NativeiOSCellRenderer : ViewCellRenderer
{
static NSString rid = new NSString("NativeCell");
public override UIKit.UITableViewCell GetCell (Xamarin.Forms.Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv)
{
var x = (NativeCell)item;
Console.WriteLine (x);
NativeiOSCell c = reusableCell as NativeiOSCell;
if (c == null) {
c = new NativeiOSCell (rid);
}
UIImage i = null;
if (!String.IsNullOrWhiteSpace (x.ImageFilename)) {
i = UIImage.FromFile ("Images/" + x.ImageFilename + ".jpg");
}
c.UpdateCell (x.Name, x.Category, i);
return c;
}
}
public class NativeiOSCell : UITableViewCell {
UILabel headingLabel, subheadingLabel;
UIImageView imageView;
public NativeiOSCell (NSString cellId) : base (UITableViewCellStyle.Default, cellId)
{
SelectionStyle = UITableViewCellSelectionStyle.Gray;
ContentView.BackgroundColor = UIColor.FromRGB (255,255,224);
imageView = new UIImageView();
headingLabel = new UILabel () {
Font = UIFont.FromName("Cochin-BoldItalic", 22f),
TextColor = UIColor.FromRGB (127, 51, 0),
BackgroundColor = UIColor.Clear
};
subheadingLabel = new UILabel () {
Font = UIFont.FromName("AmericanTypewriter", 12f),
TextColor = UIColor.FromRGB (38, 127, 0),
TextAlignment = UITextAlignment.Center,
BackgroundColor = UIColor.Clear
};
ContentView.Add (headingLabel);
ContentView.Add (subheadingLabel);
ContentView.Add (imageView);
}
public void UpdateCell (string caption, string subtitle, UIImage image)
{
imageView.Image = image;
headingLabel.Text = caption;
subheadingLabel.Text = subtitle;
}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
imageView.Frame = new CoreGraphics.CGRect(ContentView.Bounds.Width - 63, 5, 33, 33);
headingLabel.Frame = new CoreGraphics.CGRect(5, 4, ContentView.Bounds.Width - 63, 25);
subheadingLabel.Frame = new CoreGraphics.CGRect(100, 18, 100, 20);
}
}